Javascript/Blockchain/Karac : Différence entre versions

De WikiSys
Aller à : navigation, rechercher
(Tester notre Blockchain)
(communications)
(3 révisions intermédiaires par le même utilisateur non affichées)
Ligne 37 : Ligne 37 :
 
==attributs du block==
 
==attributs du block==
  
Pour les besoins de ce tutoriel, nous allons partir du principe que tout le code se trouvera dans un fichier nommé "blockchain.js". Libre à vous de séparer le code  dans plusieurs fichiers.
+
Pour les besoins de ce tutoriel, nous allons partir du principe que tout le code se trouvera dans un fichier nommé "'''blockchain.js'''".  
 +
 
 +
Libre à vous de séparer le code  dans plusieurs fichiers.
  
 
Nous allons créer une classe qui nous permettra de créer des blocks :
 
Nous allons créer une classe qui nous permettra de créer des blocks :
Ligne 54 : Ligne 56 :
 
==Le « hash » du block==
 
==Le « hash » du block==
  
En général, le hash est calculé par le minage (c'est le cas pour le Bitcoin par exemple). Comme cette étape ne sera pas implémentée dans notre exemple, nous allons simplement le générer automatiquement. Le hash est calculé en tenant compte de toutes les données du block. Cela signifie que si une donnée change, le hash du block n’est plus valide. Comme nous le verrons plus tard, deux blocks pourraient se retrouver avec le même identifiant, mais leurs hash seront uniques.
+
En général, le '''hash''' est calculé par le '''minage''' (c'est le cas pour le Bitcoin par exemple).  
 +
 
 +
Comme cette étape ne sera pas implémentée dans notre exemple, nous allons simplement le générer automatiquement. Le hash est calculé en tenant compte de toutes les données du block. Cela signifie que si une donnée change, le hash du block n’est plus valide. Comme nous le verrons plus tard, deux blocks pourraient se retrouver avec le même identifiant, mais leurs hash seront uniques.
  
 
Pour calculer ce hash, nous allons utiliser la librairie "CryptoJS" que nous téléchargerons tout à l’heure en même temps que toutes les autres (en utilisant "npm"), afin de se faciliter la tâche :
 
Pour calculer ce hash, nous allons utiliser la librairie "CryptoJS" que nous téléchargerons tout à l’heure en même temps que toutes les autres (en utilisant "npm"), afin de se faciliter la tâche :
  
var calculateHash = (index, previousHash, timestamp, data) => {
+
var calculateHash = (index, previousHash, timestamp, data) => {
 
     return CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
 
     return CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
};
+
};
  
 
Une chose importante à remarquer au sujet du hash, est qu’il rend impossible la modification d’un block sans une modification du hash de tous les blocks consécutifs, puisque le previousHash du block suivant sera modifié et que le hash de ce même block devra donc en tenir compte.
 
Une chose importante à remarquer au sujet du hash, est qu’il rend impossible la modification d’un block sans une modification du hash de tous les blocks consécutifs, puisque le previousHash du block suivant sera modifié et que le hash de ce même block devra donc en tenir compte.
 +
 
==Le genesis block==
 
==Le genesis block==
  
Ligne 250 : Ligne 255 :
 
  npm install
 
  npm install
  
Si tout s'est bien déroulé, vous devriez avoir un dossier " nodes_modules " contenant un certain nombre de sous-dossiers.
+
Si tout s'est bien déroulé, vous devriez avoir un dossier " '''nodes_modules''' " contenant un certain nombre de sous-dossiers.
  
Nous allons démarrer un serveur sur le port 3001 (noeud 1) et un autre sur le port 3002 (noeud 2) communiquant avec le premier par le biais du websocket.
+
Nous allons démarrer un serveur sur le '''port 3001''' (noeud 1) et un autre sur le '''port 3002''' (noeud 2) communiquant avec le premier par le biais du '''websocket'''.
  
 
==communications==
 
==communications==
Ligne 258 : Ligne 263 :
 
Lancez la commande suivante dans le terminal pour démarrer le premier serveur (noeud 1) :
 
Lancez la commande suivante dans le terminal pour démarrer le premier serveur (noeud 1) :
  
HTTP_PORT=3001 P2P_PORT=6001 npm start
+
'''HTTP_PORT=3001 P2P_PORT=6001 npm start'''
  
 
Ouvrez un second terminal et démarrer-y le second serveur (noeud 2) :
 
Ouvrez un second terminal et démarrer-y le second serveur (noeud 2) :
  
HTTP_PORT=3002 P2P_PORT=6002 PEERS=ws://localhost:6001 npm start
+
'''HTTP_PORT=3002 P2P_PORT=6002 PEERS=ws://localhost:6001 npm start'''
  
 
Voici alors ce qui devrait apparaître dans vos 2 terminaux ouverts :
 
Voici alors ce qui devrait apparaître dans vos 2 terminaux ouverts :
Ligne 283 : Ligne 288 :
 
</pre>
 
</pre>
  
Un des moyens les plus directs pour contrôler un nœud est de passer par Curl (que vous pouvez installer avec la commande "sudo apt install curl" sur Ubuntu par exemple).  
+
Un des moyens les plus directs pour contrôler un nœud est de passer par '''Curl''' (que vous pouvez installer avec la commande "sudo apt install curl" sur Ubuntu par exemple).  
  
Ouvrez un troisième terminal qui sera utilisé pour contrôler les noeuds. Lancez-y alors la commande suivante afin de générer un block sur le noeud 1 (port 3001) :
+
Ouvrez un '''troisième terminal''' qui sera utilisé pour contrôler les noeuds. Lancez-y alors la commande suivante afin de générer un block sur le noeud 1 (port 3001) :
  
curl -H "Content-type:application/json" --data '{"data" : "Donnees du premier block"}' http://localhost:3001/mineBlock
+
'''curl -H "Content-type:application/json" --data '{"data" : "Donnees du premier block"}' http://localhost:3001/mineBlock'''
  
 
Le block apparaît alors sur le noeud 1.
 
Le block apparaît alors sur le noeud 1.
Ligne 304 : Ligne 309 :
 
Vous pouvez maintenant essayer d'afficher tous les blocks de votre blockchain dans le 3ème terminal en tapant cette commande :
 
Vous pouvez maintenant essayer d'afficher tous les blocks de votre blockchain dans le 3ème terminal en tapant cette commande :
  
curl http://localhost:3001/blocks
+
'''curl http://localhost:3001/blocks'''
  
 
Pour lister les pairs d'un noeuds, lancez cette commande :
 
Pour lister les pairs d'un noeuds, lancez cette commande :
  
curl http://localhost:3001/peers
+
'''curl http://localhost:3001/peers'''
  
 
Si vous tapez cette commande pour les deux noeuds, vous devriez obtenir quelque chose comme cela :
 
Si vous tapez cette commande pour les deux noeuds, vous devriez obtenir quelque chose comme cela :
<pre>
+
 
curl http://localhost:3001/peers
+
'''curl http://localhost:3001/peers'''
["127.0.0.1:65173"]karac:blockchain karac$  
+
 
curl http://localhost:3002/peers
+
["127.0.0.1:65173"]karac:blockchain karac$  
["127.0.0.1:6001"]karac:blockchain karac$  
+
 
 +
'''curl http://localhost:3002/peers'''
 +
 
 +
["127.0.0.1:6001"]karac:blockchain karac$  
 
</pre>
 
</pre>
 
Enfin, vous avez la possibilité d'ajouter un pair comme ceci :  
 
Enfin, vous avez la possibilité d'ajouter un pair comme ceci :  
  
curl -H "Content-type:application/json" --data '{"peer" : "ws://localhost:6001"}' http://localhost:3001/addPeer
+
'''curl -H "Content-type:application/json" --data '{"peer" : "ws://localhost:6001"}' http://localhost:3001/addPeer'''
 
+
 
Le code complet
 
Le code complet
 
<pre>
 
<pre>
Ligne 534 : Ligne 541 :
 
initP2PServer();
 
initP2PServer();
 
</pre>
 
</pre>
 +
 
==Conclusion==
 
==Conclusion==
  

Version du 6 octobre 2019 à 16:46

Lexique

Voici quelques définitions qui vous aideront à bien comprendre le tutoriel qui va suivre :

Minage : utilisation de la puissance de calcul informatique afin de traiter des transactions, sécuriser le réseau et permettre à tous les utilisateurs du système de rester synchronisés.

Node : (= nœud en français) ordinateur relié au réseau et utilisant un programme relayant les transactions.

Fonction de hashage : fonction particulière qui, à partir d'une donnée fournie en entrée, calcule une empreinte servant à identifier rapidement la donnée initiale.

Peer-to-peer : (=P2P) modèle de réseau informatique où chaque client est aussi un serveur. Le protocole de transfert de données BitTorrent, popularisé par des programmes tels que Napster ou encore µTorrent pour la diffusion de fichiers (musiques, vidéos, etc.), en est un exemple typique d'utilisation.

Peer : (= pair en français) dans un réseau peer-to-peer, chaque internaute est un pair et peut partager ses fichiers ainsi que télécharger les fichiers des autres.

Websocket : protocole réseau visant à créer des canaux de communication bidirectionnels. Son utilisation est appropriée dans le cadre de la création d'un système de messagerie instantanée par exemple. En effet, sans ce protocole, il faudrait mettre à jour le navigateur régulièrement afin d'afficher les nouveaux messages qui auraient été envoyés sur le serveur par d'autres utilisateurs. Les fonctionnalités

Voici la liste des fonctionnalités que nous allons implémenter dans notre blockchain :

  • La structure des blocks et de la blockchain
  • Une méthode pour ajouter des blocks à la blockchain (avec des données aléatoires)
  • Communication et synchronisation des nœuds de la blockchain entre eux
  • Une API HTTP simple pour contrôler les nœuds

A noter que par souci de simplicité, nous ne mettrons pas en place de système de minage et que l’étape de la validation de chaque bloc n’aura donc pas lieu (étape numéro 3 sur le schéma). La structure du block

Pour commencer, nous allons définir la structure des blocks. Afin de préserver une certaine simplicité, nous allons nous limiter aux informations suivantes :

  • index : identifiant du block
  • timestamp : date et heure de création du block
  • data : données entrées dans le block
  • hash : clé unique basée sur le contenu du block
  • previousHash : référence au hash du block précédent

attributs du block

Pour les besoins de ce tutoriel, nous allons partir du principe que tout le code se trouvera dans un fichier nommé "blockchain.js".

Libre à vous de séparer le code dans plusieurs fichiers.

Nous allons créer une classe qui nous permettra de créer des blocks :

class Block {
    constructor(index, previousHash, timestamp, data, hash) {
        this.index = index;
        this.previousHash = previousHash.toString();
        this.timestamp = timestamp;
        this.data = data;
        this.hash = hash.toString();
    }
}

Le « hash » du block

En général, le hash est calculé par le minage (c'est le cas pour le Bitcoin par exemple).

Comme cette étape ne sera pas implémentée dans notre exemple, nous allons simplement le générer automatiquement. Le hash est calculé en tenant compte de toutes les données du block. Cela signifie que si une donnée change, le hash du block n’est plus valide. Comme nous le verrons plus tard, deux blocks pourraient se retrouver avec le même identifiant, mais leurs hash seront uniques.

Pour calculer ce hash, nous allons utiliser la librairie "CryptoJS" que nous téléchargerons tout à l’heure en même temps que toutes les autres (en utilisant "npm"), afin de se faciliter la tâche :

var calculateHash = (index, previousHash, timestamp, data) => {
   return CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
};

Une chose importante à remarquer au sujet du hash, est qu’il rend impossible la modification d’un block sans une modification du hash de tous les blocks consécutifs, puisque le previousHash du block suivant sera modifié et que le hash de ce même block devra donc en tenir compte.

Le genesis block

Aussi appelé bloc de genèse en Français, il est le premier block de la blockchain. Par conséquent, il n’a pas de previousHash. Nous allons générer ce block « en dur ».

var getGenesisBlock = () => {
   return new Block(0, "0", 1465154705, "mon genesis block !", "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7");
};

Générer un block

Pour générer un block, il faut connaître le hash du block précédent et déterminer les autres attributs du block. Nous allons définir les données du block (blockData) en tant qu’argument puisque celles-ci seront fournies par l’utilisateur. Les autres arguments peuvent être générés.

var generateNextBlock = (blockData) => {
   var previousBlock = getLatestBlock();
   var nextIndex = previousBlock.index + 1;
   var nextTimestamp = new Date().getTime() / 1000;
   var nextHash = calculateHash(nextIndex, previousBlock.hash, nextTimestamp, blockData);
   return new Block(nextIndex, previousBlock.hash, nextTimestamp, blockData, nextHash);
};

Stocker la blockchain

Nous allons utiliser un tableau JavaScript pour stocker la blockchain.

var blockchain = [getGenesisBlock()];

Valider l’intégrité des blocks

À n’importe quel moment, il faut pouvoir définir si un block ou une chaine de blocks est valide en terme d’intégrité. C’est particulièrement vrai lorsque nous recevons des nouveaux blocks depuis d’autres nœuds et que nous devons décider de les accepter ou non.

Un block est valide si :

  • l’index du block est plus grand que celui du block précédent
  • le previousHash du block correspond au hash du block précédent
  • le hash du block lui-même est valide

Voici le code qui permet de le vérifier et de lancer des messages d’erreur en cas d’invalidité :

var isValidNewBlock = (newBlock, previousBlock) => {
   if (previousBlock.index + 1 !== newBlock.index) {
       console.log('index invalide');
       return false;
   } else if (previousBlock.hash !== newBlock.previousHash) {
       console.log('previousHash invalide');
       return false;
   } else if (calculateHashForBlock(newBlock) !== newBlock.hash) {
       console.log(typeof (newBlock.hash) + ' ' + typeof calculateHashForBlock(newBlock));
       console.log('hash invalide: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash);
       return false;
   }
   return true;
};

Validons maintenant toute la blockchain en commençant par le "genesis block" puis en vérifiant chaque block avec la fonction que nous venons de créer.

var isValidChain = (blockchainToValidate) => {
   if (JSON.stringify(blockchainToValidate[0]) !== JSON.stringify(getGenesisBlock())) {
       return false;
   }
   var tempBlocks = [blockchainToValidate[0]];
   for (var i = 1; i < blockchainToValidate.length; i++) {
       if (isValidNewBlock(blockchainToValidate[i], tempBlocks[i - 1])) {
           tempBlocks.push(blockchainToValidate[i]);
       } else {
           return false;
       }
   }
   return true;
};

Choisir la chaîne la plus longue

Il doit toujours y avoir un assemblage de blocks explicite à un temps donné. En cas de conflits, on choisira la chaîne la plus longue (avec le plus grand nombre de blocks).

résolution de conflits

Le code suivant s’occupera de résoudre ce genre de problème :

var replaceChain = (newBlocks) => {
   if (isValidChain(newBlocks) && newBlocks.length > blockchain.length) {
       console.log('La blockchain reçue est valide. Remplacer la blockchain actuelle par la blockchain reçue.');
       blockchain = newBlocks;
       broadcast(responseLatestMsg());
   } else {
       console.log('La blockchain reçue est invalide.');
   }
};

Communication avec les autres nœuds

Un des points essentiels d’un nœud est de partager et synchroniser la blockchain avec d’autres nœuds afin que tout le monde puisse disposer des même données. Voici les règles que nous allons mettre en place :

  • Quand un nœud génère un nouveau block, il doit le diffuser sur le réseau
  • Quand un nœud se connecte à un pair, il émet une requête pour obtenir le dernier block
  • Quand un nœud rencontre un block qui a un plus grand index que le dernier block connu actuellement par celui-ci, il ajoute ce block a sa chaîne ou effectue une requête pour obtenir la blockchain en entier

synchronisation des noeuds

diffusion des blocks

Nous allons utiliser des websockets pour effectuer une communication « peer-to-peer ». Le socket actif pour chaque nœud est stocké dans la variable "sockets". Les pairs ne sont pas trouvés automatiquement. Leurs emplacements (= Websocket URLs) doivent être ajoutés manuellement.

Gérer un nœud

Avant d'écrire le code permettant de gérer un noeud, ajoutons une petite fonction permettant a un nouveau pair de se connecter aux pairs existants :

var connectToPeers = (newPeers) => {
   newPeers.forEach((peer) => {
       var ws = new WebSocket(peer);
       ws.on('open', () => initConnection(ws));
       ws.on('error', () => {
           console.log('échec de la connexion')
       });
   });
};

L’utilisateur doit pouvoir gérer un nœud. Il pourra faire cela via le serveur HTTP que nous allons mettre en place ici.

Le code suivant s’occupera de gérer cela avec l'aide de la librairie "Express" (que nous installeront en même temps que les autres), qui propose notamment des méthodes pour la création d'API HTTP :

var initHttpServer = () => {
   var app = express();
   app.use(bodyParser.json());
   app.get('/blocks', (req, res) => res.send(JSON.stringify(blockchain)));
   app.post('/mineBlock', (req, res) => {
       var newBlock = generateNextBlock(req.body.data);
       addBlock(newBlock);
       broadcast(responseLatestMsg());
       console.log('block ajouté : ' + JSON.stringify(newBlock));
       res.send();
   });
   app.get('/peers', (req, res) => {
       res.send(sockets.map(s => s._socket.remoteAddress + ':' + s._socket.remotePort));
   });
   app.post('/addPeer', (req, res) => {
       connectToPeers([req.body.peer]);
       res.send();
   });
   app.listen(http_port, () => console.log('Écoute HTTP sur le port : ' + http_port));
};

Voici donc les fonctions accessibles par l’utilisateur :

  • /blocks : lister les blocks
  • /mineBlock : générer un nouveau block
  • /peers : lister les pairs
  • /addPeer : ajouter un pair

Architecture

Il est important de comprendre que le nœud utilise en fait deux serveurs web : un pour que l’utilisateur puisse contrôler le nœud (serveur HTTP) et un autre pour les communication peer-to-peer entre les nœuds (serveur websocket HTTP).

http et websocket

Tester notre Blockchain

Vous êtes sans doute impatient de tester tout cela. Mais il va tout de même falloir produire un dernier effort (très petit je vous rassure), afin d’installer les librairies nécessaires à faire tourner notre système.

Pour faire fonctionner notre petite application, nous aurons besoin de « Node.js » qui permet d’exécuter du JavasScript du côté serveur. Cette plateforme a également l’avantage de fournir le gestionnaire de dépendances « npm ». Nous l'utiliseront dans quelques instants pour télécharger les librairies dont nous avons besoin pour le hash ou le websocket par exemple.

Une fois que tout cela est installé, nous allons créer un fichier nommé « package.json » dans lequel nous allons mettre ces quelques lignes :

package.json
 {
  "name": "blockchain",
  "version": "1.0.0",
  "description": "",
  "scripts": {
	"start": "node blockchain.js"
  },
  "dependencies": {
	"body-parser": "^1.15.2",
	"crypto-js": "^3.1.6",
	"express": "~4.11.1",
	"ws": "^1.1.0"
  },
  "engines": {
	"node": ">=4.3.2"
  }
 }

Notez que pour nous simplifier la vie, nous avons fait en sorte que la commande "npm start" lance directement le fichier " blockchain.js " contenant le code de notre blockchain.

Démarrons maintenant l’installation des librairies en ouvrant un terminal au dossier dans lequel nous avons tous les fichiers de notre blockchain avec la commande suivante :

npm install

Si tout s'est bien déroulé, vous devriez avoir un dossier " nodes_modules " contenant un certain nombre de sous-dossiers.

Nous allons démarrer un serveur sur le port 3001 (noeud 1) et un autre sur le port 3002 (noeud 2) communiquant avec le premier par le biais du websocket.

communications

Lancez la commande suivante dans le terminal pour démarrer le premier serveur (noeud 1) :

HTTP_PORT=3001 P2P_PORT=6001 npm start

Ouvrez un second terminal et démarrer-y le second serveur (noeud 2) :

HTTP_PORT=3002 P2P_PORT=6002 PEERS=ws://localhost:6001 npm start

Voici alors ce qui devrait apparaître dans vos 2 terminaux ouverts :

Premier serveur (noeud 1) :

Écoute du port websocket p2p sur : 6001
Écoute HTTP sur le port : 3001
Message Reçu{"type":0}
Message Reçu{"type":2,"data":"[{\"index\":0,\"previousHash\":\"0\",\"timestamp\":1465154705,\"data\":\"mon genesis block !\",\"hash\":\"816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7\"}]"}
La blockchain reçue est plus courte que la blockchain actuelle. Ne rien faire.

Second serveur (noeud 2) :

Écoute du port websocket p2p sur : 6002
Écoute HTTP sur le port : 3002
Message Reçu{"type":0}
Message Reçu{"type":2,"data":"[{\"index\":0,\"previousHash\":\"0\",\"timestamp\":1465154705,\"data\":\"mon genesis block !\",\"hash\":\"816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7\"}]"}
La blockchain reçue est plus courte que la blockchain actuelle. Ne rien faire.

Un des moyens les plus directs pour contrôler un nœud est de passer par Curl (que vous pouvez installer avec la commande "sudo apt install curl" sur Ubuntu par exemple).

Ouvrez un troisième terminal qui sera utilisé pour contrôler les noeuds. Lancez-y alors la commande suivante afin de générer un block sur le noeud 1 (port 3001) :

curl -H "Content-type:application/json" --data '{"data" : "Donnees du premier block"}' http://localhost:3001/mineBlock

Le block apparaît alors sur le noeud 1.

Message Reçu{"type":2,"data":"[{\"index\":1,\"previousHash\":\"816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7\",\"timestamp\":1517241931.658,\"data\":\"Donnees du premier block\",\"hash\":\"ed1ca6044ddcd53806e954bbfe178c256bb99bc21f1a8ef894739f9e3dcf8a0b\"}]"} La blockchain reçue est plus courte que la blockchain actuelle. Ne rien faire.

Le noeud 1 le diffuse alors aux autres noeuds (noeud 2) qui l'ajoute à sa chaîne :

Message Reçu{"type":2,"data":"[{\"index\":1,\"previousHash\":\"816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7\",\"timestamp\":1517241931.658,\"data\":\"Donnees du premier block\",\"hash\":\"ed1ca6044ddcd53806e954bbfe178c256bb99bc21f1a8ef894739f9e3dcf8a0b\"}]"} Dernier block de la blockchain : 0. Block reçu par le pair : 1 Nous pouvons appondre le block reçu à notre chaîne

Celui-ci diffuse à son tour le nouveau block aux autres noeuds (noeud 1), d'où la phrase "La blockchain reçue est plus courte que la blockchain actuelle. Ne rien faire." qui s'affiche dans le noeud 1.

Vous pouvez maintenant essayer d'afficher tous les blocks de votre blockchain dans le 3ème terminal en tapant cette commande :

curl http://localhost:3001/blocks

Pour lister les pairs d'un noeuds, lancez cette commande :

curl http://localhost:3001/peers

Si vous tapez cette commande pour les deux noeuds, vous devriez obtenir quelque chose comme cela :

curl http://localhost:3001/peers

["127.0.0.1:65173"]karac:blockchain karac$ 

curl http://localhost:3002/peers

["127.0.0.1:6001"]karac:blockchain karac$ 

</pre> Enfin, vous avez la possibilité d'ajouter un pair comme ceci :

curl -H "Content-type:application/json" --data '{"peer" : "ws://localhost:6001"}' http://localhost:3001/addPeer Le code complet

'use strict';
var CryptoJS = require("crypto-js");
var express = require("express");
var bodyParser = require('body-parser');
var WebSocket = require("ws");

var http_port = process.env.HTTP_PORT || 3001;
var p2p_port = process.env.P2P_PORT || 6001;
var initialPeers = process.env.PEERS ? process.env.PEERS.split(',') : [];

class Block {
    constructor(index, previousHash, timestamp, data, hash) {
        this.index = index;
        this.previousHash = previousHash.toString();
        this.timestamp = timestamp;
        this.data = data;
        this.hash = hash.toString();
    }
}

var sockets = [];
var MessageType = {
    QUERY_LATEST: 0,
    QUERY_ALL: 1,
    RESPONSE_BLOCKCHAIN: 2
};

var getGenesisBlock = () => {
    return new Block(0, "0", 1465154705, "mon genesis block !", "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7");
};

var blockchain = [getGenesisBlock()];

var initHttpServer = () => {
    var app = express();
    app.use(bodyParser.json());

    app.get('/blocks', (req, res) => res.send(JSON.stringify(blockchain)));
    app.post('/mineBlock', (req, res) => {
        var newBlock = generateNextBlock(req.body.data);
        addBlock(newBlock);
        broadcast(responseLatestMsg());
        console.log('block ajouté : ' + JSON.stringify(newBlock));
        res.send();
    });
    app.get('/peers', (req, res) => {
        res.send(sockets.map(s => s._socket.remoteAddress + ':' + s._socket.remotePort));
    });
    app.post('/addPeer', (req, res) => {
        connectToPeers([req.body.peer]);
        res.send();
    });
    app.listen(http_port, () => console.log('Écoute HTTP sur le port : ' + http_port));
};


var initP2PServer = () => {
    var server = new WebSocket.Server({port: p2p_port});
    server.on('connection', ws => initConnection(ws));
    console.log('Écoute du port websocket p2p sur : ' + p2p_port);

};

var initConnection = (ws) => {
    sockets.push(ws);
    initMessageHandler(ws);
    initErrorHandler(ws);
    write(ws, queryChainLengthMsg());
};

var initMessageHandler = (ws) => {
    ws.on('message', (data) => {
        var message = JSON.parse(data);
        console.log('Message Reçu' + JSON.stringify(message));
        switch (message.type) {
            case MessageType.QUERY_LATEST:
                write(ws, responseLatestMsg());
                break;
            case MessageType.QUERY_ALL:
                write(ws, responseChainMsg());
                break;
            case MessageType.RESPONSE_BLOCKCHAIN:
                handleBlockchainResponse(message);
                break;
        }
    });
};

var initErrorHandler = (ws) => {
    var closeConnection = (ws) => {
        console.log('échec de la connexion au pair : ' + ws.url);
        sockets.splice(sockets.indexOf(ws), 1);
    };
    ws.on('close', () => closeConnection(ws));
    ws.on('error', () => closeConnection(ws));
};


var generateNextBlock = (blockData) => {
    var previousBlock = getLatestBlock();
    var nextIndex = previousBlock.index + 1;
    var nextTimestamp = new Date().getTime() / 1000;
    var nextHash = calculateHash(nextIndex, previousBlock.hash, nextTimestamp, blockData);
    return new Block(nextIndex, previousBlock.hash, nextTimestamp, blockData, nextHash);
};


var calculateHashForBlock = (block) => {
    return calculateHash(block.index, block.previousHash, block.timestamp, block.data);
};

var calculateHash = (index, previousHash, timestamp, data) => {
    return CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
};

var addBlock = (newBlock) => {
    if (isValidNewBlock(newBlock, getLatestBlock())) {
        blockchain.push(newBlock);
    }
};

var isValidNewBlock = (newBlock, previousBlock) => {
    if (previousBlock.index + 1 !== newBlock.index) {
        console.log('index invalide');
        return false;
    } else if (previousBlock.hash !== newBlock.previousHash) {
        console.log('previousHash invalide');
        return false;
    } else if (calculateHashForBlock(newBlock) !== newBlock.hash) {
        console.log(typeof (newBlock.hash) + ' ' + typeof calculateHashForBlock(newBlock));
        console.log('hash invalide: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash);
        return false;
    }
    return true;
};

var connectToPeers = (newPeers) => {
    newPeers.forEach((peer) => {
        var ws = new WebSocket(peer);
        ws.on('open', () => initConnection(ws));
        ws.on('error', () => {
            console.log('échec de la connexion')
        });
    });
};

var handleBlockchainResponse = (message) => {
    var receivedBlocks = JSON.parse(message.data).sort((b1, b2) => (b1.index - b2.index));
    var latestBlockReceived = receivedBlocks[receivedBlocks.length - 1];
    var latestBlockHeld = getLatestBlock();
    if (latestBlockReceived.index > latestBlockHeld.index) {
        console.log('Dernier block de la blockchain : ' + latestBlockHeld.index + '. Block reçu par le pair : ' + latestBlockReceived.index);
        if (latestBlockHeld.hash === latestBlockReceived.previousHash) {
            console.log("Nous pouvons appondre le block reçu à notre chaîne");
            blockchain.push(latestBlockReceived);
            broadcast(responseLatestMsg());
        } else if (receivedBlocks.length === 1) {
            console.log("Nous devons interroger notre chaîne depuis notre pair");
            broadcast(queryAllMsg());
        } else {
            console.log("La blockchain reçue est plus longue que la blockchain actuelle");
            replaceChain(receivedBlocks);
        }
    } else {
        console.log('La blockchain reçue est plus courte que la blockchain actuelle. Ne rien faire.');
    }
};

var replaceChain = (newBlocks) => {
    if (isValidChain(newBlocks) && newBlocks.length > blockchain.length) {
        console.log('La blockchain reçue est valide. Remplacer la blockchain actuelle par la blockchain reçue.');
        blockchain = newBlocks;
        broadcast(responseLatestMsg());
    } else {
        console.log('La blockchain reçue est invalide.');
    }
};

var isValidChain = (blockchainToValidate) => {
    if (JSON.stringify(blockchainToValidate[0]) !== JSON.stringify(getGenesisBlock())) {
        return false;
    }
    var tempBlocks = [blockchainToValidate[0]];
    for (var i = 1; i < blockchainToValidate.length; i++) {
        if (isValidNewBlock(blockchainToValidate[i], tempBlocks[i - 1])) {
            tempBlocks.push(blockchainToValidate[i]);
        } else {
            return false;
        }
    }
    return true;
};

var getLatestBlock = () => blockchain[blockchain.length - 1];
var queryChainLengthMsg = () => ({'type': MessageType.QUERY_LATEST});
var queryAllMsg = () => ({'type': MessageType.QUERY_ALL});
var responseChainMsg = () =>({
    'type': MessageType.RESPONSE_BLOCKCHAIN, 'data': JSON.stringify(blockchain)
});
var responseLatestMsg = () => ({
    'type': MessageType.RESPONSE_BLOCKCHAIN,
    'data': JSON.stringify([getLatestBlock()])
});

var write = (ws, message) => ws.send(JSON.stringify(message));
var broadcast = (message) => sockets.forEach(socket => write(socket, message));

connectToPeers(initialPeers);
initHttpServer();
initP2PServer();

Conclusion

Ce tutoriel montre comment réaliser une blockchain simple à des fins d'apprentissage. Comme il n'y a pas d'algorithme de minage, il ne sera pas possible de l'utiliser sur un réseau publique. Néanmoins, elle possède les caractéristiques de base d'une blockchain fonctionnelle.

Si vous souhaitez poursuivre l'expérience avec l'algorithme de minage, les transactions, et même le "wallet" (programme servant à stocker les clés privées des crypto-monnaies), je vous invite à lire la suite de l'article de base de Lauri Hartikka sur lequel s'appuie ce tutoriel (en anglais). Si vous vous lancez dans cette aventure, je vous rends attentif au fait que le code décrit dans le tutoriel date un peu et a subi quelques mises à jour. N'hésitez donc pas à consulter le Github de l'auteur (chaque chapitre ayant sa propre branche).

Sources

https://lhartikk.github.io/jekyll/update/2017/07/14/chapter1.html

https://blockchainfrance.net/decouvrir-la-blockchain/c-est-quoi-la-blockchain/

https://blockchainfrance.net/le-lexique-de-la-blockchain/

https://www.crypto-france.com/quest-ce-que-la-blockchain-explication-simple-fonctionnement/ Catégories : Tech web

Tags : JavaScript innovation crypto-monnaie