This tutorial is based on this Hackernoon Tutorial from Salanfe
Proof-of-Authority is a consensus mechanism used as an alternative to Proof of Work. Where Proof of Work relies on miners expending computing power in a race to create the next block and secure the blockchain, Proof of Authority designates this role to a number of "authority" nodes. In a typical Proof of Authority network, one node or a series of nodes act as validators for the entire network.
Proof-of-Authority (PoA) is an easier way to run a blockchain with semi-trusted participants, such as a consortium blockchain. Designated signers create the possibility of a small, secure blockchain not worried about 51% attacks endemic to Proof-of-Work secured blockchains. PoA also comes with transaction finality, so a new node syncing to the PoA network only has to obtain the latest block to know the state of the network. However, PoA-backed network have their own series of security issues (which are outside the scope of this tutorial).
Note: It is not suggested that you use this tutorial PoA network created for anything other than educational purposes. For secure PoA networks, see Further Reading section below
Objectives:
- To understand how a Proof of Authority consensus algorithm works.
- Learn to implement a PoA using
Geth
's Clique module - Run and conduct transactions on the PoA network
- Project Setup
- Configuring Clique via Puppeth
- Starting the Network
- Transactions
- Adding or Removing Signers From the Network
- Further-Reading
We are assuming you have already downloaded the Go-Ethereum client geth
. If you have not, installation instructions are here.
Note: This tutorial was created using geth 1.9.2
Please fork this repo and download it to your local machine. Use the terminal to cd
into the directory geth-poa-tutorial
.
geth
comes with a native Proof-of-Authority protocol called Clique
. It creates a genesis block detailing the important specifications of a Proof-of-Authority network. These include:
- Who are the valid signers of blocks in this network?
- Which accounts are pre-funded in this network?
- How often will a block be broadcast by valid signers?
Clique has an important security feature. Any signer can only produce a certain number of consecutive blocks. That number is contingent on the number of total number of signers encoded in the genesis block. The equation for that number is CONSECUTIVE_BLOCK_LIMIT = (NUMBER_OF_TOTAL_SIGNERS / 2) + 1
For example, if there are four nodes designated as signers in the genesis block, one of those signers can only broadcast (4 / 2) + 1 = 3
blocks before the network will refuse any more blocks confirmed by that node. Only until another one of the four designated signers confirms a block can the original node resume submitting.
This creates a mild safeguard against a rogue node overtaking a network and maliciously altering the network state. It also, however, can freeze your network if not enough valid signers are online.
Fun Fact: The Rinkeby testnet runs using Clique-generated Proof-of-Authority.
For more information about Clique, please read this Github issue from Péter Szilágyi explaining its release.
geth
includes the handy module puppeth
for creating custom genesis blocks. We have scripted that process here but will walk through what puppeth
is doing under-the-hood.
For local caching purposes, puppeth
asks you for a network name. If it detects this network has been used before, it will pull the network information it has previously stored.
Be sure not to use any spaces, hyphens or capital letters!
We're getting a new genesis block started, so we're going to type 2
and hit enter.
Ethereum Mainnet currently runs on Proof-of-Work consensus. That's an awesome consensus mechanism for large networks sprawling the globle in a trustless manner, but for our small, down-home network, we're going to choose Clique, a Proof-of-Authority consensus mechanism.
In blockchain networks, the block time is considered the "network heartbeat" — how often a confirmed block containing the latest confirmed transactions is broadcast out to the network.
In Proof-of-Work on Ethereum and Bitcoin, this time is moderated by a complex algorithm which has a target network time (~10m for Bitcoin, ~14s for Ethereum). It adjusts variables according to the current capacity of miners on the network.
In Proof-of-Authority, we don't need that complicated algorithm but we do need a set time to run an orderly network. Since Ethereum's block time is 12-14 seconds, we'll put our network's block time at 7 seconds.
Proof-of-Authority networks can decrease their block time and therefore increase their transaction throughput (the number of transactions processed per second). This is a desirable goal for certain blockchain use cases.
As discussed above, Proof-of-Authority networks allow only certain nodes, called "sealers" to confirm blocks in the network. Furthermore, the Clique consensus protocol only allows each sealer to confirm a certain number of consecutive blocks. For the sake of demonstrating Proof-of-Authority networks, we'll just put the one below for simplicity's sake:
0x1a4b71b48498237d2817be049b4bc43fad971bca
Note: The 0x
prefix hexadecimal-encoded values to help program parsers. Read more here
Cryptocurrency units can be created one of two ways. First, someone can mine new blocks for a network and be rewarded in that cryptocurrency. Second, the creator of a network can designate certain accounts to have a certain balance in the genesis block (also known as a "pre-mine").
Here, we are designating the same account above as "pre-funded" in the genesis block. What generous folks we are!
This is an interesting technical point beyond the scope of this tutorial. See more here if you're interested in why we need to fund precompiles. And answer yes
.
Nodes on a network need to determine which nodes they can connect with. With a custom-generated genesis block, we provide a Network ID
so nodes can easily identify each other.
1515
we are choosing randomly, you can pick any number you'd like.
After we enter Network ID
, puppeth
takes all the information we have provided it and creates a custom genesis block for our Proof-of-Authority network.
Last, we need to export the genesis block puppeth
has created for us. Select 2
from the current menu (see above)
Select 2
again (see above)
Last, simply press enter to have the genesis block saved to the default setting of the current directory. Don't mind the errors you see there -- Aleth and Parity are two Ethereum clients that don't have support for Clique, the PoA consensus mechanism we picked.
We copy this file and put it in the directory of the three nodes we'll be launching.
If you haven't done so already, please fork this repo and download it to your local machine. Use your terminal to cd
into the repo's directory folder. We will assume you are in geth-poa-terminal
for the instructions below.
While in the directory geth-poa-tutorial
, please type (or copy paste) the following command and press enter:
bash ./bootnode/bootnode-start-local.sh
This terminal window is now our window into the networking elements of our PoA blockchain. The geth
client will attempt to find other nodes via this bootnode (as described here). We have asked it to provide all the information it receives and gives so we can watch the nodes come online and discover each other.
Note: The bootnode acts more as a network router or hub. It is a central point the nodes can contact to pass through their information and receive other nodes' information. It does not contain our custom genesis block.
I would recommend enlarging the window and placing it in the upper corner of the window, as shown below:
Now, we will launch our first node. We have designated this to be the only sealer node, so it will be able to start mining blocks immediately.
Leave the bootnode terminal window open and create a new terminal window. I recommend placing it under the open bootnode terminal
From geth-poa-tutorial
in the new terminal window, please enter this command:
cd node1
Then, please enter this command:
geth --datadir ./ init poa.json
You should see the following:
We have told the geth
client: "We'd like you to initialize the Ethereum protocol using this custom genesis block we've created." This is very important for the tutorial.
We can see we've been successful when client returns:
Successfully wrote genesis state
Now, we'd like to actually start the Ethereum protocol running on our custom PoA genesis block. To do that, please enter the following command (this is also provided locally in PoA Workshop Commands.txt
located within the geth-poa-tutorial
directory):
geth --datadir ./ --syncmode 'full' --port 30311 --rpc --rpcaddr '0.0.0.0' --rpccorsdomain "*" --rpcport 8502 --rpcapi 'personal,db,eth,net,web3,txpool,miner' --bootnodes 'enode://ea2cab82d19b0704299ff837c9e10ee90841d24503e2f6d993fafbf351d9b6a1860cb6f20eee0f35412c4c28ca68c0720f623792f24abdf2ad0d386598a5b4e2@127.0.0.1:30310' --networkid 1515 --gasprice '1' --allow-insecure-unlock -unlock 1a4b71b48498237d2817be049b4bc43fad971bca --password password.txt --mine
If all is successful, you should see two things happen:
- The Ethereum node running on
node1
should start to mine blocks for the network and - The bootnode window will light up with traffic.
The second phenomenon is node1
communicating with the bootnode...
Node 1: '>>>PING' ("Are you there, bootnode? It's me, Node 1, running a custom protocol")
...
Bootnode: '<<<PONG' ("Yes, I'm here, Node 1")
...And trying to locate other nodes within the network...
Node 1: '>>> NEIGHBORS' ("Do you know of any other nodes like me?")
...
Bootnode: (`<<< FINDNODE`) ("Here's what I know about other nodes.")
Here's what you should see:
We're going to do the same thing with Node 2, with a few changes.
Open a third terminal screen and go to the geth-poa-tutorial
directory. Type in this familiar command:
cd node2
Initialize geth
with our custom genesis block:
geth --datadir ./ init poa.json
Actually start the Ethereum protocol by entering the slightly-different-but-still-long command below (available locally in the node2
directory under geth-start-local.txt
):
NOTE: THE COMMAND BELOW LOOKS SIMILAR TO NODE 1 BUT IT IS DIFFERENT! YOU MUST COPY AND PASTE THE COMMAND BELOW FOR NODE 2
geth --datadir ./ --syncmode 'full' --port 30312 --rpc --rpcaddr '0.0.0.0' --rpccorsdomain "*" --rpcport 8503 --rpcapi 'personal,db,eth,net,web3,txpool,miner' --bootnodes 'enode://ea2cab82d19b0704299ff837c9e10ee90841d24503e2f6d993fafbf351d9b6a1860cb6f20eee0f35412c4c28ca68c0720f623792f24abdf2ad0d386598a5b4e2@127.0.0.1:30310' --networkid 1515 --gasprice '1' --allow-insecure-unlock -unlock f59a61caf69f7216b83f063c2b9b712b82e50e84 --password password.txt
Again, you should see the node activity begin. You should also see more activity on the bootnode screen, this time registering requests from Node 1 AND Node 2:
NOTE: You do not see any mining activity from Node 2 because Node 2 is not a valid signer node. You can run it as a miner, but any blocks it submits, even valid ones, will be rejected from the network.
Open a fourth terminal, and you know the routine by now:
cd node3
Initialize geth
with our custom genesis block:
geth --datadir ./ init poa.json
Copy and paste this UNIQUE NODE 3 COMMAND to actually start the protocol:
geth --datadir ./ --syncmode 'full' --port 30313 --rpc --rpcaddr '0.0.0.0' --rpccorsdomain "*" --rpcport 8504 --rpcapi 'personal,db,eth,net,web3,txpool,miner' --bootnodes 'enode://ea2cab82d19b0704299ff837c9e10ee90841d24503e2f6d993fafbf351d9b6a1860cb6f20eee0f35412c4c28ca68c0720f623792f24abdf2ad0d386598a5b4e2@127.0.0.1:30310' --networkid 1515 --gasprice '1' --allow-insecure-unlock -unlock 6dcccc3ab843cf7973986870fbffe55fca71acbd --password password.txt
If all goes well, geth
will boot up and a new node pop up in the bootnode window:
An Ethereum client creates an endpoint to connect with whatever blockchain its running. geth
provides a Javascript-based, REPL command-line interface to connect and interact with that endpoint. We can connect to that console and interact on our newly-created PoA blockchain.
In the terminal window for Node 2, I open a new tab within the window using command + T
. In that window, I navigate back to geth-poa-tutorial/node2
and type the following command:
geth attach geth.ipc
If successful, you will see the Javascript Runtime start with a REPL console line:
Test to make sure you're connected to the network by running a few commands:
admin.peers
Will deliver a list of known peers (should be two, and the IDs should correspond with the traffic you see on your bootnode console). Here's what mine looked like:
Here are some other commands to help you get your bearings:
net.version
Should return our custom network ID of 1515
eth.blockNumber
Should return the latest block mined by Node 1 (be careful of capitalization in Javascript!)
eth.coinbase
Should return the Ethereum address for the node you are currently operating
Open the document labeled poa-accounts.txt
to see the Ethereum addresses for Node 1, Node 2 and Node 3.
All these windows can get confusing! If you're unsure about which node's console you're in, simple enter eth.coinbase
into the geth console
tab to see the Ethereum address.
Navigate to the geth console
tab on your Node 1 terminal.
The command below is a generic one to send ether (the cryptocurrency unit for Ethereum networks) from one account to another account. Substitute 'RECEIPIENT_NODE_ADDRESS'
with the forty-character address (surrounded by quotes ''
but with the 0x
prefix) of where you'd like to send it (For example, if you're Node 1, that will be Node 2 or Node 3).
eth.sendTransaction({'from':eth.coinbase, 'to':''RECEIPIENT_NODE_ADDRESS'', 'value':web3.toWei(13.5, 'ether')})
If it's successful, the console will print a long string, which is your Receipt Transaction Hash
. It's like your transaction ID that you can use to look up your transaction, and its status, later.
To see the changes caused by the transaction, enter this command into all three nodes' geth console
:
web3.fromWei(eth.getBalance(eth.coinbase), "ether")
The balances should be different by a factor of 13.5 for the two nodes involved in the transaction and the same as the pre-fund for the node not included.
The protocol defines a voting mechanism to dynamically add new signers and remove existing ones. In Geth this can be controlled via the clique.propose(address, authorized) method (clique_propose for remote RPC calls).
To authorize a new signer, existing ones can propose it via
clique.propose("0x...", true)
. When more than half the signers proposed it, the authorization comes into effect immediately and the new account can start signing blocks.Similarly, existing signers can propose deauthorizing existing ones via
clique.propose("0x...", false).
Again if half + 1 signers deauthorize a signer, it is immediately removed from the list and blocks minted by it are rejected from that point onward.
This tutorial aimed to explain the basics of Proof-of-Authority, show you how to start your own PoA private network and conduct transactions on that network. We hope you enjoyed it!
For more resources, please see the links below:
-
This tutorial is based on this post — "Setup Your Own Private Proof of Authority Ethereum Network with Geth"
-
"Proof of Authority Chains," Parity Technologies
-
"Proof-of-Authority Tutorial," Parity Technologies
-
"Using Puppeth to Manually Create an Ethereum Proof-of-Authority Clique Network using AWS," Collin Cusce
-
Clique PoA Protocol Details, Péter Szilágyi & Ethereum Foundation
-
Cliquebait Repo, from FOAM