Définition du consensus Proof Of Authority (POA, Preuve d’autorité):

Proof-of-authority (PoA) est un algorithme utilisé avec la technologie de blockchain qui fournit des transactions relativement rapides grâce à un mécanisme de consensus basé sur l’identité en tant que pieu (l’identité étant une preuve suffisante pour la validation des transactions).

Dans les réseaux basés sur le POA, les transactions et les blocs sont validés par des comptes approuvés, appelés validateurs. Les validateurs exécutent un logiciel leur permettant de mettre des transactions dans des blocs. Le processus est automatisé et n’exige pas que les validateurs surveillent constamment leurs ordinateurs. Cependant, il est nécessaire de maintenir l’ordinateur (le nœud d’autorité, ou l’ensemble des noeuds d’autorité) pour qu’ils ne soient pas compromis.

Avec les POA, les individus gagnent le droit de devenir des validateurs, donc il y a une incitation à conserver la position qu’ils ont acquis. En attachant une réputation à l’identité, les validateurs sont encouragés à maintenir le processus de transaction, car ils ne souhaitent pas que leur identité soit liée à une réputation négative. [Source: Wikipedia]

Comment monter notre propre chaîne avec le consensus POA ?

Buts de ce tutoriel: Créer deux noeuds, en utilsant Geth, qui vont servire de validateurs de notre blockchain privée d’Ethereum. Et on va monter un bootnode qui va servir de service de découverte (discovery service). Et le tout formera un réseau d’Ethereum privé en P2P (Peer to Peer).

Avant de continuer la lecture, je tiens à vous inviter à lire la proposition EIP255 qui a fait naître la possibilité d’intégrer la POA avec la blockchain d’Ethereum déjà existante.

Dans ce tutoriel, je vais utiliser Geth v1.7.3, vous pouvez l’installer en visitant ce lien:

$> geth version
Geth
Version: 1.7.3-stable
Git Commit: 4bb3c89d44e372e6a9ab85a8be0c9345265c763a
Architecture: amd64
Protocol Versions: [63 62]
Network Id: 1
Go Version: go1.9.2
Operating System: linux
GOPATH=/home/user/go
GOROOT=/usr/user/go

PS: Si vous utilisez Ubuntu 16.04+ ou une distribution dérivée d’Ubuntu, vous pouvez télécharger Geth en ajoutant le PPA d’Ethereum [Pour plus d’informations, veuillez visiter ce lien]:

$> sudo apt update
$> sudo add-apt-repository -y ppa:ethereum/ethereum
$> sudo apt update
$> sudo apt-get install ethereum

Maintenant, créons un nouveau espace de travail:

$> cd  # Aller au dossier de l'utilisateur
$> mkdir test_poa # Créer un nouveau dossier
$> cd test_poa
$> mkdir node1 node2

Ensuite, on va créer des comptes Ethereum avec Geth pour les noeuds 1 et 2:

$> geth --datadir node1/ account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat passphrase: # ici on écrit le mot de passe pour le compte n°1 du noeud 1
Address: {2d299ed001270f8c2f02abdda2bda4e5b4cca348} # adresse publique du compte n°1
$> geth --datadir node2/ account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat passphrase: # ici on écrit le mot de passe pour le compte n°2 du noeud 2
Address: {bbb6abec9f9b6d0e7d3ff18bcb68ce6ac931478f} # adresse publique du compte n°2

Ces commandes vont créer un nouveau dossier sous chacun des dossiers node1 et node2 nommé keystore qui va contenir un fichier qui va servir du compte ethereum pour le noeud n°1 et le noeud n°2.

À ce niveau, copions les deux adresses publiques, générées par Geth, dans un nouveau fichier nommé accounts :

echo -n "2d299ed001270f8c2f02abdda2bda4e5b4cca348" >> accounts
echo -n "bbb6abec9f9b6d0e7d3ff18bcb68ce6ac931478f" >> accounts

Et écrivons les mots de passes de chaque compte de chaque noeud dans un fichier nommé password sous chaque dossier de chaque noeud:

echo -n "node1 password" > node1/password
echo -n "node2 password" > node2/password

Miantenant, créons le fichier Genesis qui va servir à initier notre nouvelle blockchain privée (pour plus d’informations sur le Genesis block veuillez visiter ce lien)

Pour ne pas compliquer les choses, Geth vient avec un exécutable nommé puppeth qui facilite la création des fichier Genesis qui vont initier les nouvelles blockchain privées.

PS: Si vous ne trouvez pas l’exécutable puppeth, vous pouvez l’installer via le PPA d’ethereum qu’on a vu au début de ce tutoriel:

$> sudo apt update
$> sudo apt install puppeth

À ce niveau, créons le fichier Genesis:

$> puppeth
+-----------------------------------------------------------+
| Welcome to puppeth, your Ethereum private network manager |
|                                                           |
| This tool lets you create a new Ethereum network down to  |
| the genesis block, bootnodes, miners and ethstats servers |
| without the hassle that it would normally entail.         |
|                                                           |
| Puppeth uses SSH to dial in to remote servers, and builds |
| its network components out of Docker containers using the |
| docker-compose toolset.                                   |
+-----------------------------------------------------------+

Please specify a network name to administer (no spaces or hyphens, please)
> NexusNetwork # Nom de notre nouveau Network (nouvelle blockchain) (PS: Pas d'espaces !)

Sweet, you can set this via --network=NexusNetwork next time!

INFO [05-29|14:06:10] Administering Ethereum network           name=NexusNetwork
WARN [05-29|14:06:10] No previous configurations found         path=/home/user/.puppeth/NexusNetwork

What would you like to do? (default = stats)
 1. Show network stats
 2. Configure new genesis
 3. Track new remote server
 4. Deploy network components
> 2 # choisir n° 2

Which consensus engine to use? (default = clique)
 1. Ethash - proof-of-work
 2. Clique - proof-of-authority
> 2 # choisir le consensus POA qui est le n°2

How many seconds should blocks take? (default = 15)
> 3 # le temps entre chaque bloc

Which accounts are allowed to seal? (mandatory at least one) # seal = validateur
> 0x2d299ed001270f8c2f02abdda2bda4e5b4cca348 # adresse publique du compte du noeud n°1
> 0xbbb6abec9f9b6d0e7d3ff18bcb68ce6ac931478f # adresse publique du compte du noeud n°2
> 0x

Which accounts should be pre-funded? (advisable at least one) # Ajouter des fonds aux adresses
> 0x2d299ed001270f8c2f02abdda2bda4e5b4cca348 # adresse publique du compte du noeud n°1
> 0xbbb6abec9f9b6d0e7d3ff18bcb68ce6ac931478f # adresse publique du compte du noeud n°2
> 0x

Specify your chain/network ID if you want an explicit one (default = random)
> 9999 # ID de notre réseau
INFO [05-29|14:07:30] Configured new genesis block 

What would you like to do? (default = stats)
 1. Show network stats
 2. Manage existing genesis
 3. Track new remote server
 4. Deploy network components
> 2 # choisir 2

 1. Modify existing fork rules
 2. Export genesis configuration
 3. Remove genesis configuration
> 2 # choisir 2 pour exporter le fichier Genesis nommé NexusNetwork.json

Which file to save the genesis into? (default = NexusNetwork.json)
> 
INFO [05-29|14:08:09] Exported existing genesis block 

What would you like to do? (default = stats)
 1. Show network stats
 2. Manage existing genesis
 3. Track new remote server
 4. Deploy network components
> ^C

L’arboraissance de notre projet sera comme suit:

tree -L 2
.
├── accounts
├── NexusNetwork.json
├── node1
│   ├── keystore
│   └── password
└── node2
    ├── keystore
    └── password

4 directories, 4 files

Et le contenu du fichier NexusNetwork.json est:

$> cat NexusNetwork.json 
{
  "config": {
    "chainId": 9999,
    "homesteadBlock": 1,
    "eip150Block": 2,
    "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "eip155Block": 3,
    "eip158Block": 3,
    "byzantiumBlock": 4,
    "clique": {
      "period": 3,
      "epoch": 30000
    }
  },
  "nonce": "0x0",
  "timestamp": "0x5b0d504f",
  "extraData": "0x00000000000000000000000000000000000000000000000000000000000000002d299ed001270f8c2f02abdda2bda4e5b4cca348bbb6abec9f9b6d0e7d3ff18bcb68ce6ac931478f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "gasLimit": "0x47b760",
  "difficulty": "0x1",
  "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "coinbase": "0x0000000000000000000000000000000000000000",
  "alloc": {
    "0000000000000000000000000000000000000000": {
      "balance": "0x1"
    },
    "0000000000000000000000000000000000000001": {
      "balance": "0x1"
    ...
    "bbb6abec9f9b6d0e7d3ff18bcb68ce6ac931478f": {
      "balance": "0x200000000000000000000000000000000000000000000000000000000000000"
    }
  },
  "number": "0x0",
  "gasUsed": "0x0",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

Ceci dit, initions nos noeuds avec le nouveau fichier Genesis:

$> geth --datadir node1/ init NexusNetwork.json 
INFO [05-29|14:10:14] Allocated cache and file handles         database=/home/user/test_poa/node1/geth/chaindata cache=16 handles=16
INFO [05-29|14:10:14] Writing custom genesis block 
INFO [05-29|14:10:14] Successfully wrote genesis state         database=chaindata                                  hash=78e1c1…31bcdd
INFO [05-29|14:10:14] Allocated cache and file handles         database=/home/user/test_poa/node1/geth/lightchaindata cache=16 handles=16
INFO [05-29|14:10:14] Writing custom genesis block 
INFO [05-29|14:10:14] Successfully wrote genesis state         database=lightchaindata                                  hash=78e1c1…31bcdd

$>geth --datadir node2/ init NexusNetwork.json 
INFO [05-29|14:10:20] Allocated cache and file handles         database=/home/user/test_poa/node2/geth/chaindata cache=16 handles=16
INFO [05-29|14:10:20] Writing custom genesis block 
INFO [05-29|14:10:20] Successfully wrote genesis state         database=chaindata                                  hash=78e1c1…31bcdd
INFO [05-29|14:10:20] Allocated cache and file handles         database=/home/user/test_poa/node2/geth/lightchaindata cache=16 handles=16
INFO [05-29|14:10:20] Writing custom genesis block 
INFO [05-29|14:10:20] Successfully wrote genesis state         database=lightchaindata 

Puis, créons un boot.key en utilisant bootnode un exécutable trouvé dans le projet Geth et si cet exécutable est manquant, vous pouvez le télécharger via les dépôts du PPA d’Ethereum ajouté au début de l’article:

$> sudo apt update
$> sudo apt install bootnode

Et la génération du boot.key:

$> bootnode -genkey boot.key
$> cat boot.key 
7dd0375ae3f0e92cc151829e98289ccde4b9c2cb2317bd15283d6b930f47bfc4

Et la nouvelle arboraissance du projet sera:

tree -L 2
.
├── accounts
├── boot.key
├── NexusNetwork.json
├── node1
│   ├── geth
│   ├── keystore
│   └── password
└── node2
    ├── geth
    ├── keystore
    └── password

6 directories, 5 files

Maintenant, dans une terminale, exécutons notre bootnode:

bootnode -nodekey boot.key -verbosity 9 -addr :30310
INFO [05-29|14:16:50] UDP listener up                          self=enode://393fafe1972bb56d3743a7d0d5590c113c357348815f10b8d970ffa3919b71be734abf8ebeff2eb2921eee305ba4c2fefbbd79ef3a69e4a78750491da3824d78@[::]:30310

Et dans une autre terminale, exécutons le noeud n°1:

$> geth --datadir node1/ --syncmode 'full' --port 30311 --rpc --rpcaddr 'localhost' --rpcport 8501 --rpcapi 'personal,net,db,eth,web3,txpool,miner' --bootnodes 'enode://393fafe1972bb56d3743a7d0d5590c113c357348815f10b8d970ffa3919b71be734abf8ebeff2eb2921eee305ba4c2fefbbd79ef3a69e4a78750491da3824d78@127.0.0.1:30310' -networkid 9999 --gasprice '1' --unlock '2d299ed001270f8c2f02abdda2bda4e5b4cca348' --password node1/password --mine

Et dans une autre terminale exécutons le noeud n°2:

$> geth --datadir node1/ --syncmode 'full' --port 30311 --rpc --rpcaddr 'localhost' --rpcport 8501 --rpcapi 'personal,db,eth,web3,txpool,miner' --bootnodes 'enode://393fafe1972bb56d3743a7d0d5590c113c357348815f10b8d970ffa3919b71be734abf8ebeff2eb2921eee305ba4c2fefbbd79ef3a69e4a78750491da3824d78@127.0.0.1:30310' -networkid 9999 --gasprice '1' --unlock '2d299ed001270f8c2f02abdda2bda4e5b4cca348' --password node1/password --mine

En ouvrant la terminale qu’on a utilisé pour exécuter notre bootnode, on va constater que le deux noeuds sont activées vu que les adresses 127.0.0.1:30311 et 127.0.0.1:30312 sont activées:

TRACE[05-29|14:28:25] >> NEIGHBORS/v4                          addr=127.0.0.1:30311 err=nil
TRACE[05-29|14:28:25] << FINDNODE/v4                           addr=127.0.0.1:30311 err=nil
TRACE[05-29|14:28:25] >> PING/v4                               addr=127.0.0.1:30312 err=nil
TRACE[05-29|14:28:25] << PONG/v4                               addr=127.0.0.1:30312 err=nil

Et en ouvrant une autre terminale pour voir la nouvelle arboraissance du projet quand les noeuds et le bootnode sont activés, on va remarquer la génération de deux nouveaux fichiers geth.ipc qui vont servire à activer la communication IPC avec notre nouvelle blockchain:

tree -L 2
.
├── accounts
├── boot.key
├── NexusNetwork.json
├── node1
│   ├── geth
│   ├── geth.ipc
│   ├── keystore
│   └── password
└── node2
    ├── geth
    ├── geth.ipc
    ├── keystore
    └── password

6 directories, 7 files

Essayons d’activer la console JavaScript de Geth en utilisant IPC comme moyen de communication:

geth attach node1/geth.ipc 
Welcome to the Geth JavaScript console!

instance: Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9.2
coinbase: 0x2d299ed001270f8c2f02abdda2bda4e5b4cca348
at block: 80 (Tue, 29 May 2018 14:32:08 CET)
 datadir: /home/user/test_poa/node1
 modules: admin:1.0 clique:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

> # Console activée avec succès !

De même essayons d’activer la console JavaScript de Geth en utilsant le protocole RPC/HTTP comme moyen de communication:

geth attach 'http://localhost:8501'
Welcome to the Geth JavaScript console!

instance: Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9.2
coinbase: 0x2d299ed001270f8c2f02abdda2bda4e5b4cca348
at block: 104 (Tue, 29 May 2018 14:33:20 CET)
 modules: eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

> # Console activée avec succès !

PS: On peut noter que Geth nous dise qu’il a ouvert le port 8501 pour la communication RPC/HTTP et le endpoint IPC pour une interaction avec ses fonctionnalités. Par exemple dans le noeud n°1 en lançant les commandes en dessus on peut constater ce message:

INFO [05-29|14:24:21] IPC endpoint opened: /home/user/test_poa/node1/geth.ipc 
INFO [05-29|14:24:21] HTTP endpoint opened: http://localhost:8501 

Maintenant, essayons de communiquer avec notre nouvelle blockchain en utilisant la console JavaScript de Geth:

$> geth attach 'http://localhost:8501'
Welcome to the Geth JavaScript console!

instance: Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9.2
coinbase: 0x2d299ed001270f8c2f02abdda2bda4e5b4cca348
at block: 136 (Tue, 29 May 2018 14:35:29 CET)
 modules: eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

> net.version
"9999" # version de notre réseau
> eth.blockNumber
153 # le dernier block trouvé
> eth.coinbase
"0x2d299ed001270f8c2f02abdda2bda4e5b4cca348" # adresse du compte par défaut
> eth.sendTransaction({'from': eth.coinbase, 'to': 'bbb6abec9f9b6d0e7d3ff18bcb68ce6ac931478f', 'value': web3.toWei(1, 'ether')})
"0x9c896d9e26d9671d4dfe56861e05b01b18380a48905de77234b5569e554fa349" # tx hash
> eth.getTransactionReceipt("0x9c896d9e26d9671d4dfe56861e05b01b18380a48905de77234b5569e554fa349")
{
  blockHash: "0x7de8cc81dc7d5a9839b63f5ce47b05c5fddc6347f8df30fd4f8357217a1b562b",
  blockNumber: 189,
  contractAddress: null,
  cumulativeGasUsed: 21000,
  from: "0x2d299ed001270f8c2f02abdda2bda4e5b4cca348",
  gasUsed: 21000,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  status: "0x1",
  to: "0xbbb6abec9f9b6d0e7d3ff18bcb68ce6ac931478f",
  transactionHash: "0x9c896d9e26d9671d4dfe56861e05b01b18380a48905de77234b5569e554fa349",
  transactionIndex: 0
}
> exit

De la même façon, essayons de comminiquer avec notre nouvelle blockchain en utilisant Python et web3(v4.2.1):

PS: Veuillez visiter ce lien pour activer le middleware qui va faciliter la communication avec notre nouvelle blockchain avec le consensus POA.

>>> from web3 import Web3, IPCProvider
>>> w3 = Web3(IPCProvider('/home/user/test_poa/node1/geth.ipc'))
>>> from web3.middleware import geth_poa_middleware
>>> w3.middleware_stack.inject(geth_poa_middleware, layer=0)
>>> w3.version.node
'Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9.2'
>>> w3.eth.coinbase
'0x2d299ed001270F8C2f02abDDa2Bda4E5B4CCa348'
>>> w3.eth.gasPrice
1 # en wei
>>> w3.eth.getBalance("0x2d299ed001270F8C2f02abDDa2Bda4E5B4CCa348")
904625697166532776746648320380374280103671755200316906557262375061821304312 # en wei
>>> w3.fromWei(w3.eth.getBalance("0x2d299ed001270F8C2f02abDDa2Bda4E5B4CCa348"), 'ether')
Decimal('904625697166532776746648320380374280103671755200316906557.262375061821304312') # en ether

Au final: On a pu créer une blockchain privée d’ethereum avec le consensus POA qui peut nous être utile pour le cas où on cherche à créer un réseau blockchain sans GAS/sans frais de transaction ou avec des frais moins élevés ou bien en montant des noeuds spécifiques qui seront les seuls participants de confiance du réseau et bien sûr l’un des atouts du POA c’est qu’il n’utilise pas des réssources énormes pour la validation des blocs (à l’instar du consensus POW, Proof Of Work).

Ceci dit, ce type de réseau est utile pour les tests en local, comme pour des solutions pour des organismes privés qui cherchent à monter une solution blockchain en limitant l’accès à des membres bien définis.