Quorum how-to

The original article may be consulted at https://github.com/ConsenSys/qbc/blob/master/docs/HOWTO.md

Setting up your own Quorum network

Set up the Quorum node network

Generate Enode and nodekey

Each node in the network is identified by a unique id assigned to it called the enode. This enode is the public key corresponding to a private nodekey.

Generate public enode from the private nodekey:

nodekey=`docker run -v <PATH TO DATA FOLDER>:/var/qdata/ consensys/quorum:latest sh -c "/opt/bootnode -genkey /var/qdata/dd/nodekey -writeaddress;cat /var/qdata/dd/nodekey"`;
enode=`docker run -v <PATH TO DATA FOLDER>:/var/qdata/ consensys/quorum:latest sh -c "/opt/bootnode -nodekeyhex $nodekey -writeaddress"`;

Only nodes who's enodes are listed in the static-nodes.json file can participate in the consensus mechanism.

Here is an example of how to generate a static-nodes.json file:

ips=("10.5.0.15" "10.5.0.16" "10.5.0.17" "10.5.0.18")
i=1
mkdir -p $WORKDIR/q1/dd/
echo "[" > $WORKDIR/q1/dd/static-nodes.json;
for ip in ${ips[*]}; do
    mkdir -p $WORKDIR/q${i}/logs;
    mkdir -p $WORKDIR/q${i}/dd/{keystore,geth};
    enode=`docker run -v $WORKDIR/q${i}:/var/qdata/ consensys/quorum:latest sh -c "/opt/bootnode -genkey /var/qdata/dd/nodekey -writeaddress; cat /var/qdata/dd/nodekey"`;
    enode=`docker run -v $WORKDIR/q${i}:/var/qdata/ consensys/quorum:latest sh -c "/opt/bootnode -nodekeyhex $enode -writeaddress"`;
    sep=`[[ $i < ${#ips[@]} ]] && echo ","`;
    echo '  "enode://'$enode'@'$ip':21000?discport=0"'$sep >> $WORKDIR/q${i}/dd/static-nodes.json;
    let i++;
done
echo "]" >> $WORKDIR/q1/dd/static-nodes.json

The output should look something like this:

$ cat $WORKDIR/q1/dd/static-nodes.json
[
  "enode://07f75277b1bb17329d91dde84d2e4d2d01d67b50a8e6974fbc19602edd3a832b@10.5.0.15:21000?discport=0",
  "enode://48ef4d4bdcb04db9bb0095dde90ed49abb4be995b6c673e8e2715e3c0cb34614@10.5.0.16:21000?discport=0",
  "enode://bf94844598cbfe955952076ba046ed143fec160968eed12d3fa93256c6e7a8b0@10.5.0.17:21000?discport=0",
  "enode://14d2b9dc41c34638bf736cd84d43b30e733d94a98e60190ee760c6b73548c26c@10.5.0.18:21000?discport=0"
]

Create an initial account

Run: /opt/geth --datadir=$WORKDIR/dd account new

You will be prompted for a password.

Keep a copy of the address returned by the program. You can also see the private key has been stored as a file under $WORKDIR/dd/keystore.

Create a genesis file

All nodes should have in common the first block (the genesis block) and a set of common parameters to operate the network.

An example of genesis JSON file is in this repository under tests/crux_quorum/istanbul-genesis.json.

Please edit the genesis file and add your account to it with a non-zero balance, so you have some Ether when you start the chain.

The JSON file is ingested by the geth init command to initialize the first block.

docker run -it -v <PATH TO DATA FOLDER>:/var/qdata/ -v <PATH TO JSON FILE>:/tmp/genesis.json \ consensys/quorum:latest /opt/geth --datadir /var/qdata/dd init /tmp/genesis.json

Create a list of static and permissioned nodes

As you create the Quorum network, you will need to organize your nodes so they can connect to each other.

You can use two separate files to organize the network:

static-nodes.json: this file contains the list of nodes this Quorum instance will connect to.

permissioned-nodes.json: nodes listed in this file are explicitly allowed to send data to the Quorum instance.

Both files have the same format. Here is an example.

["enode://[email protected]:21000?discport=0, "enode://[email protected]:21000?discport=0]

Each enode URI is built with the public key of the node, associated with its host name and RPC port. The discport parameter is set to zero as no discovery is performed on the network.

Quorum. data folder structure

Create the folders as follows:

mkdir -p dd/keystore logs

Copy the files created earlier so it conforms to the structure below:

├── dd
│   ├── keystore
│   │   └── key
│   ├── permissioned-nodes.json
│   └── static-nodes.json
├── logs
│   └── node.log
├── nodekey
└── passwords.txt

Set up Tessera or Crux nodes

Generate Constellation keys

For each Constellation instance, you will need a unique keypair.

This command generates a keypair under /tmp/out.key and /tmp/out.pub.

docker run -v /tmp:/tmp -it consensys/crux:latest /opt/crux --generate-keys /tmp/out

Constellation data folder structure

Create the logs folder: mkdir -p logs

Copy the files created earlier so it conforms to the structure below. Make sure to rename the keypair to tm.key and tm.pub respectively.

├── logs
│   └── crux.log
├── tm.key
└── tm.pub

Running the Quorum network

Running Docker

On each node participating in the network, you will need to run Quorum and Constellation (either Crux or Tessera).

Assuming you followed the instructions above, you should have a data folder for Quorum and Constellation respectively.

You can then start Crux with:

env HOSTNAME=<hostname of the crux node> OTHER_NODES=http://<hostname of an other crux node to discover> docker run -p 9000:9000 -v <path to constellation data>:/var/cdata/ <path to quorum data>:/var/qdata/ -it consensys/crux:latest

You can start Quorum with:

docker run -p 22000:22000 -p 21000:21000 -v <path to quorum data>:/var/qdata/ consensys/quorum:latest

You can also use a docker compose yaml configuration file to run both containers together:

version: "3.4"
services:
  crux:
    image: consensys/crux:latest
    container_name: crux
    ports:
      - 9000:9000
    restart: always
    environment:
      - HOSTNAME=<hostname of the crux node>
      - OTHER_NODES=<hostname of an other crux node to discover>
    volumes:
      - ${WORKDIR}/c1:/var/cdata/
      - ${WORKDIR}/q1:/var/qdata/
  node:
    image: consensys/quorum:latest
    container_name: quorum
    ports:
      - 22000:22000
      - 21000:21000
    restart: always
    volumes:
      - ${WORKDIR}/q1:/var/qdata/

Check the network is up and running.

On the node, perform the following to check Constellation is up and running:

curl -vv http://localhost:19001/upcheck

Check the quorum logs to check the node came up without issues.

less <path to quorum data>/logs/node.log

Private Transactions

Example of how to send a private transaction:

Send Private Transaction

$ curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from": "0xed9d02e382b34818e88b88a309c7fe71e65f419d", "to": "0xca843569e3427144cead5e4d5999a3d0ccf92b8e", "gas": "0x76c0", "data": "0xca843569e3427144cead5e4d5999a3d0ccf92b8eed9d02e382b34818e88b88a309c7fe71e65f419d", "privateFor": ["QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc="]}],"id":1}' 0.0.0.0:22001

{"jsonrpc":"2.0","id":1,"result":"0xdcbe81138963dc32993ec0a83f6d974e1e3c0ec27fc88f09bfe8c7b54ab51de1"}

Copy 0xdcbe81138963dc32993ec0a83f6d974e1e3c0ec27fc88f09bfe8c7b54ab51de1 from the result field for the next step

Get Transaction Input

$ curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0xdcbe81138963dc32993ec0a83f6d974e1e3c0ec27fc88f09bfe8c7b54ab51de1"],"id":1}' 0.0.0.0:22001
{"jsonrpc":"2.0","id":1,"result":{"blockHash":"0xe98fc1b2110f60407e9bfefddadf16e5b210b9e12dd0e78b4fe277552078c353","blockNumber":"0x58","from":"0xed9d02e382b34818e88b88a309c7fe71e65f419d","gas":"0x76c0","gasPrice":"0x0","hash":"0xdcbe81138963dc32993ec0a83f6d974e1e3c0ec27fc88f09bfe8c7b54ab51de1","input":"0x7e5b0b4effedf9a3d8c393e2e669a73296066032a0c779d28d5b35234be5da2d529a096efadc8944cc4dfd9bae4ed1efe75c7f7b431584a35dc38c942177884e","nonce":"0x0","to":"0xca843569e3427144cead5e4d5999a3d0ccf92b8e","transactionIndex":"0x0","value":"0x0","v":"0x25","r":"0x174ba76866bea2d7520e004e3f9b573ca058e219347880fc86ab70b4a708a4dc","s":"0x30eba5e128884ce1f5a659b8e978d62d9d438173d2f3cc11c0020f7cdd6a48a5"}}

Copy 0x7e5b0b4effedf9a3d8c393e2e669a73296066032a0c779d28d5b35234be5da2d529a096efadc8944cc4dfd9bae4ed1efe75c7f7b431584a35dc38c942177884e from the input field for the next step

Get Payload from Sending node

$ curl -X POST --data '{"jsonrpc":"2.0", "method":"eth_getQuorumPayload", "params":["0x7e5b0b4effedf9a3d8c393e2e669a73296066032a0c779d28d5b35234be5da2d529a096efadc8944cc4dfd9bae4ed1efe75c7f7b431584a35dc38c942177884e"], "id":2}' 0.0.0.0:22001

{"jsonrpc":"2.0","id":2,"result":"0xca843569e3427144cead5e4d5999a3d0ccf92b8eed9d02e382b34818e88b88a309c7fe71e65f419d"}

Get Payload from Receiving node

$ curl -X POST --data '{"jsonrpc":"2.0", "method":"eth_getQuorumPayload", "params":["0x7e5b0b4effedf9a3d8c393e2e669a73296066032a0c779d28d5b35234be5da2d529a096efadc8944cc4dfd9bae4ed1efe75c7f7b431584a35dc38c942177884e"], "id":2}' 0.0.0.0:22001

{"jsonrpc":"2.0","id":2,"result":"0xca843569e3427144cead5e4d5999a3d0ccf92b8eed9d02e382b34818e88b88a309c7fe71e65f419d"}

For all other nodes, 0x should be the result:

0x means that the privateFor public key doesn't match any nodes in the network

Troubleshooting

Open a shell to a container:

docker exec -it <container_name> /bin/bash

Node Info

curl -X POST --data '{"jsonrpc":"2.0","method":"admin_nodeInfo","id":1}' 0.0.0.0:22001

Get list of connected peers

curl -X POST --data '{"jsonrpc":"2.0","method":"admin_peers","id":1}' 0.0.0.0:22001

Get blocknumber

curl -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' 0.0.0.0:22001