Skip to main content

Use Quorum Developer Quickstart

The Quorum Developer Quickstart uses the GoQuorum Docker image to run a private IBFT network of GoQuorum nodes managed by Docker Compose.


This tutorial runs a private network suitable for education or demonstration purposes and is not intended for running production networks.



Allow Docker up to 4G of memory or 6G if running the privacy examples. Refer to the Resources section in Docker for Mac and Docker Desktop for details.

Generate the tutorial blockchain configuration files

To create the tutorial docker-compose files and artifacts, run:

npx quorum-dev-quickstart

Follow the prompts displayed to run GoQuorum and logging with ELK. Enter n for Codefi Orchestrate and y for private transactions.

Start the network

To start the network, go to the installation directory (quorum-test-network if you used the default value) and run:


The script builds the Docker images, and runs the Docker containers.

Four GoQuorum IBFT validator nodes and a non-validator node are created to simulate a base network. In addition, there are three member pairs (GoQuorum and Tessera sets) to simulate private nodes on the network.

When execution is successfully finished, the process lists the available services:

Quorum Dev Quickstart
List endpoints and services
JSON-RPC HTTP service endpoint : http://localhost:8545
JSON-RPC WebSocket service endpoint : ws://localhost:8546
Web block explorer address : http://localhost:25000/
Prometheus address : http://localhost:9090/graph
Cakeshop toolkit address : http://localhost:8999
Grafana address : http://localhost:3000/d/a1lVy7ycin9Yv/goquorum-overview?orgId=1&refresh=10s&from=now-30m&to=now&var-system=All
Collated logs using Grafana Loki : http://localhost:3000/d/Ak6eXLsPxFemKYKEXfcH/quorum-logs-loki?orgId=1&var-app=quorum&var-search=

For more information on the endpoints and services, refer to in the installation directory.

To display the list of endpoints again, run:


Use a block explorer

The quickstart supports a modified version of the Alethio Ethereum Lite Explorer and BlockScout.

Alethio Ethereum Lite Explorer

Access the Alethio Ethereum Lite Explorer at http://localhost:25000 as displayed when starting the private network.

The block explorer displays a summary of the private network, indicating four peers.

Click the block number to the right of Best Block to display the block details:

Block Details

You can explore blocks by clicking on the blocks under Bk on the left-hand side.

You can search for a specific block, transaction hash, or address by clicking the 🔍 in the top left-hand corner.

Explorer Search


At the prompt Do you wish to enable support for monitoring your network with BlockScout?, enter Y to start BlockScout at http://localhost:26000.


BlockScout's Docker image is resource heavy when running. Ensure you have adequate CPU resources dedicated to the container.

The quickstart BlockScout configuration is available as a reference for your own network.

Monitor nodes with Prometheus and Grafana

The sample network also includes Prometheus and Grafana monitoring tools to let you visualize node health and usage. You can directly access these tools from your browser at the addresses displayed in the endpoint list.

For more details on how to configure and use these tools for your own nodes, see our performance monitoring documentation, the Prometheus documentation and Grafana documentation.


and collated logs via Grafana Loki


Run JSON-RPC requests

You can run JSON-RPC requests on:

  • HTTP with http://localhost:8545.
  • WebSockets with ws://localhost:8546.

Run with curl

This tutorial uses curl to send JSON-RPC requests over HTTP.

Request the node version

Run the following command from the host shell:

curl -X POST --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}' -H 'Content-Type: application/json' http://localhost:8545

The result displays the client version of the running node:

"jsonrpc": "2.0",
"id": 1,
"result": "Geth/node5-istanbul/v1.9.20-stable-1d7926a1(quorum-v21.4.2)/linux-amd64/go1.15.5"

Successfully calling this method shows that you can connect to the nodes using JSON-RPC over HTTP.

From here, you can walk through more interesting requests demonstrated in the rest of this section, or skip ahead to Create a transaction using MetaMask.

Count the peers

Peers are the other nodes connected to the node receiving the JSON-RPC request.

Poll the peer count using net_peerCount:

curl -X POST --data '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":1}' -H 'Content-Type: application/json'  http://localhost:8545

The result indicates seven peers (our validators):

"jsonrpc": "2.0",
"id": 1,
"result": "0x7"

Request the most recent block number

Call eth_blockNumber to retrieve the number of the most recently synchronized block:

curl -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' -H 'Content-Type: application/json' http://localhost:8545

The result indicates the highest block number synchronized on this node.

"jsonrpc": "2.0",
"id": 1,
"result": "0x2a"

Here the hexadecimal value 0x2a translates to decimal as 42, the number of blocks received by the node so far, about two minutes after the new network started.

Private transactions

This example uses the web3.js library to make the API calls, creating three member nodes pairs (a GoQuorum node which has a corresponding Tessera node for privacy) that can be accessed using APIs on the following ports:

Member1Quorum RPC: http://localhost:20000
Member1Tessera: http://localhost:9081

Member2Quorum RPC: http://localhost:20002
Member1Tessera: http://localhost:9082

Member3Quorum RPC: http://localhost:20004
Member1Tessera: http://localhost:9083

Navigate to the smart_contracts/privacy directory and deploy the private transaction:

cd smart_contracts/privacy
npm install
node scripts/private_tx.js

This deploys the contract and sends an arbitrary value (47) from Member1 to Member3.

Once done, it performs a read operation on the contract using the get function and the contract's ABI, at the address specified.

It then performs a write operation using the set function and the contract's ABI, at the address and sets the value to 123.

Lastly, it performs a read operation on all three members to verify that this is private between Member1 and Member3 only, and you should see that only Member1 and Member3 return the result of 123, and Member2 has an undefined value.

node scripts/private_tx.js
The transaction hash is: 0x4d796b2ccac109fc54006105df44c519341696fa88e004ce5c614239cb9f92a2
Address of transaction: 0x695Baaf717370fcBb42aB45CD83C531C27D79eF1
Use the smart contracts 'get' function to read the contract's constructor initialized value ..
Member1 obtained value at deployed contract is: 47
Use the smart contracts 'set' function to update that value to 123 .. - from member1 to member3
Verify the private transaction is private by reading the value from all three members ..
Member1 obtained value at deployed contract is: 123
Member3 obtained value at deployed contract is: 123
Member2 obtained value at deployed contract is: undefined

Inspect the member nodes with geth attach

You can inspect any of the GoQuorum nodes by using to open the geth JavaScript console.

Use a separate terminal window for each of Member1, Member2, and Member3. In each terminal, go to the main directory where docker-compose.yml is located, then:

  • In terminal 1, run ./ 1 to attach to Member1.
  • In terminal 2, run ./ 2 to attach to Member2.
  • In terminal 3, run ./ 3 to attach to Member3.

To view the private transaction, run the following command in one of the terminals:

); // replace with your transaction hash

The v field value of "0x25" or "0x26" (37 or 38 in decimal) indicates this transaction has a private payload (input).

Read the contract with get()

For each of the three nodes, create a variable called address using the geth console, and assign to it the address of the contract created by Member1. The contract address can be found:

  • In Member1's log file data/logs/1.log.
  • Using eth.getTransactionReceipt(txHash), where txHash is the hash printed to the terminal after sending the transaction. The contract address is found in the result parameter contractAddress. It is also printed in the terminal when the private transaction is processed.

After identifying the contract address, run the following command in each terminal:

var address = "0x695Baaf717370fcBb42aB45CD83C531C27D79eF1"; // replace with your contract address

Use eth.contract to define a contract class with the simpleStorage ABI definition in each terminal:

var address = "0x695baaf717370fcbb42ab45cd83c531c27d79ef1"; // replace with your address
var abi = [
constant: true,
inputs: [],
name: "storedData",
outputs: [{ name: "", type: "uint256" }],
payable: false,
type: "function",
constant: false,
inputs: [{ name: "x", type: "uint256" }],
name: "set",
outputs: [],
payable: false,
type: "function",
constant: true,
inputs: [],
name: "get",
outputs: [{ name: "retVal", type: "uint256" }],
payable: false,
type: "function",
{ inputs: [{ name: "initVal", type: "uint256" }], type: "constructor" },
var private = eth.contract(abi).at(address);

The function calls are available on the contract instance, and you can call those methods on the contract.

Get the value of the contract to confirm that only Member1 and Member3 can see the value.

  • In terminal window 1 (Member1):

  • In terminal window 2 (Member2):

  • In terminal window 3 (Member3):


Member2 can't read the state. Look in smart_contracts/privacy/node scripts/private_tx.js to confirm that 123 was the value set when the contract was updated.

Write to the contract with set()

Have Member1 set the state to the value 200 and confirm that only Member1 and Member3 can view the new state.

In terminal window 1 (Member1):

# send to Member3

You can check the log files in data/logs/ to see each node validating the block with this new private transaction. Once the block containing the transaction is validated, you can check the state from each of the members.

  • In terminal window 1 (Member1):

  • In terminal window 2 (Member2):

  • In terminal window 3 (Member3):


Member2 can't read the state.

All nodes are validating the same blockchain of transactions, with the private transactions containing only a 512-bit hash in place of the transaction data, and only the parties to the private transactions can view and update the state of the private contracts.


GoQuorum supports two types of permissions:

  • Basic permissioning: Controls which peers can connect to a given node. This permissioning method is specific to a given node, and is configured by placing a file with a list of allowed peers (similar to static-nodes.json), called permissioned-nodes.json in the data directory of a GoQuorum node.
  • Enhanced Permissions is a smart-contract-based permissioning model designed for enterprise-level needs and is applicable to all peers on the network.

This example deploys permissioning contracts then uses API calls to set appropriate permissions.

First we'll select a GuardianAccount or an admin account. In this example we'll use the account of Validator1 which has an address of 0xed9d02e382b34818e88b88a309c7fe71e65f419d, and we'll connect to Validator1's RPC endpoint

The next step is to compile the contracts and deploy them in a specific order. The contracts directory (smart_contracts/permissioning/contracts) has two versions of contracts.

  • version 1: Permissioning rules are applied during transaction entry using the permissioning data stored in node memory.
  • version 2: Permissioning rules are applied at the during transaction entry and block minting using the data stored in the permissioning contracts. This tutorial uses version 2 because it is more robust and suited for enterprise use.

The folder contains the following contracts:

  • PermissionsInterface.sol: Provides an external interface, and internal proxy to PermissionsImplementation.sol.
  • PermissionsImplementation.sol: Contains the logic of the permissions-related functionality.
  • OrgManager.sol: Implements logic for all organization management functionality.
  • AccountManager.sol: Implements logic for all account management functionality.
  • NodeManager.sol: Contains the node management functionality.
  • RoleManager.sol:Implements logic for all role management functionality.
  • VoterManager.sol: Implements account voter and voting functionality.

Navigate to the smart_contracts/permissioning directory and then compile the contracts:

cd smart_contracts/permissioning
npm install

Next, deploy the contracts and once complete, execute the init function of PermissionsUpgradable.sol with the GuardianAccount address specified earlier. This is wrapped up into a single script that you can run:

node ./scripts/deploy.js

When completed, a permission-config.json file is created which contains the addresses of the contracts and any accounts that serve as admins. In this example we've used all accounts, if you deploy these contracts in a production network, please select only those which need to be admins.

The file looks similar to:

"permissionModel": "v2",
"upgradableAddress": "0x1932c48b2bf8102ba33b4a6b545c32236e342f34",
"interfaceAddress": "0x4d3bfd7821e237ffe84209d8e638f9f309865b87",
"implAddress": "0xfe0602d820f42800e3ef3f89e1c39cd15f78d283",
"nodeMgrAddress": "0x8a5e2a6343108babed07899510fb42297938d41f",
"accountMgrAddress": "0x9d13c6d3afe1721beef56b55d303b09e021e27ab",
"roleMgrAddress": "0x1349f3e1b8d71effb47b840594ff27da7e603d17",
"voterMgrAddress": "0xd9d64b7dc034fafdba5dc2902875a67b5d586420",
"orgMgrAddress" : "0x938781b9796aea6376e40ca158f67fa89d5d8a18",
"nwAdminOrg": "ADMINORG",
"nwAdminRole" : "ADMIN",
"orgAdminRole" : "ORGADMIN",
"accounts":["0xed9d02e382b34818e88b88a309c7fe71e65f419d", "0xca843569e3427144cead5e4d5999a3d0ccf92b8e", ....],
"subOrgBreadth" : 3,
"subOrgDepth" : 4


  • permissionModel is the Permission model used (v1 or v2).
  • upgradableAddress is the address of the deployed PermissionsUpgradable.sol contract.
  • interfaceAddress is the address of the deployed PermissionsInterface.sol contract.
  • implAddress is the address of the deployed PermissionsImplementation.sol contract.
  • nodeMgrAddress is the address of the deployed NodeManager.sol contract.
  • accountMgrAddress is the address of the deployed AccountManager.sol contract.
  • roleMgrAddress is the address of the deployed RoleManager.sol contract.
  • voterMgrAddress is the address of the deployed VoterManager.sol contract.
  • orgMgrAddress is the address of the deployed OrgManager.sol contract.
  • nwAdminOrg - Name of the initial organization to be created as a part of the network boot up with a new permissions model. This organization owns all the initial nodes and network administrator accounts at network boot up.
  • nwAdminRole - Role ID assigned to the network administrator accounts.
  • orgAdminRole - Role ID assigned to the organization administrator account.
  • accounts - Initial list of accounts linked to the network administrator organization and assigned the network administrator role. These accounts have complete control of the network and can propose and approve new organizations into the network.
  • subOrgBreadth - Number of sub-organizations that any organization can have.
  • subOrgDepth - Maximum depth of the sub-organization hierarchy allowed in the network.

The last step is to copy the above permission-config.json into the data directory of each GoQuorum node and restart the nodes. This can be done by running the script:


Once the network starts up you can use the API methods to add or remove permissions.

Important considerations for production

During network initialization a few things happen:

  • A network admin organization is created with the nwAdminOrg name that was specified in the permission-config.json. All nodes which are part of the static-nodes.json will be automatically assigned to this organization
  • A network admin role is created with the nwAdminRole name specified in the permission-config.json
  • All accounts given in the accounts array of the permission-config.json file are assigned the network admin role. These accounts can propose and approve new organizations in the network.

Use Remix

You can connect your nodes to Remix by using the GoQuorum Plugin. Follow the instructions for activating the remix plugin in Getting Started, using the GoQuorum and Tessera URLs in the Private transactions section.

Use Cakeshop

Cakeshop allows you to perform transactions directly using the UI.

  1. Open http://localhost:8999 in your browser.
  2. Select the Contracts tab and Deploy the contract registry.
  3. Go to the Sandbox, select the SimpleStorage sample contract from the Contract Library, and deploy with Private For set to the second node's public key (QfeDAys9MPDs2XHExtc84jKGHxZg/aj52DTh0vtA3Xc=).
  4. Return to the main Cakeshop page, go to the Contracts tab again, and you should be able to see the contract you just deployed.
  5. Interact with it from there, and switch between nodes using the dropdown in the top right corner of the page.

Create a transaction using MetaMask

You can use MetaMask to send a transaction on your private network.

Security warning

Do not use the test accounts on Ethereum Mainnet or any production network.

The following accounts are test accounts and their private keys are publicly visible in this
documentation and in publicly available source code.

They are not secure and everyone can use them.

**Using test accounts on Ethereum Mainnet and production networks can lead to loss of funds and identity fraud.**

In this documentation, we only provide test accounts for ease of testing and learning purposes;
never use them for other purposes.

**Always secure your Ethereum Mainnet and any production account properly.**

See for instance [MyCrypto "Protecting Yourself and Your Funds" guide](
Test Account 1 (address 0xfe3b557e8fb62b89f4916b721be55ceb828dbd73)

Private key to copy :


Initial balance : 200 Eth (200000000000000000000 Wei)

Test Account 2 (address 0x627306090abaB3A6e1400e9345bC60c78a8BEf57)

Private key to copy :


Initial balance : 90000 Eth (90000000000000000000000 Wei)

Test Account 3 (address 0xf17f52151EbEF6C7334FAD080c5704D77216b732)

Private key to copy :


Initial balance : 90000 Eth (90000000000000000000000 Wei)

After importing an existing test account, create another test account from scratch to use as the recipient for a test Ether transaction.

In MetaMask, select the new test account and copy its address.

In the Block Explorer, search for the new test account by clicking on the 🔍 and pasting the test account address into the search box.

The new test account displays with a zero balance.

Send test Ether from the first test account (containing test Ether) to the new test account (which has a zero balance).


You can use a zero gas price here as this private test network is a free gas network, but the maximum amount of gas that can be used (the gas limit) for a value transaction must be at least 21000.

Refresh the Block Explorer page in your browser displaying the target test account.

The updated balance reflects the transaction completed using MetaMask.

Smart contract and dapp usage

You can use a demo dapp called Pet Shop, provided by Truffle.

The dapp runs a local website using Docker, and uses smart contracts deployed on the network.

The directory created by quorum-dev-quickstart includes a dapps directory with a pet-shop subdirectory, which contains the source code for the dapp, including the smart contracts, website, and configurations to run this tutorial.

With the blockchain running and MetaMask connected to Localhost 8545, run the following command to start the Pet Shop dapp:

cd dapps/pet-shop

The script:

  1. Installs the dapp Node dependencies (you may see some warnings here, but it will not prevent the dapp from running).
  2. Compiles the contracts.
  3. Deploys the contracts to the blockchain.
  4. Runs the tests.
  5. Builds and runs a Docker image to serve the dapp website.
Sample output
./ example output
Compiling your contracts...
> Compiling ./contracts/Adoption.sol
> Compiling ./contracts/Migrations.sol
> Artifacts written to /home/jfernandes/workspace/quorum-dev-quickstart/quorum-test-network/dapps/pet-shop/pet-shop-box/build/contracts
> Compiled successfully using:
- solc: 0.5.16+commit.9c3226ce.Emscripten.clang

Compiling your contracts...
> Everything is up to date, there is nothing to compile.

Starting migrations...
> Network name: 'quickstartWallet'
> Network id: 1337
> Block gas limit: 700000000 (0x29b92700)


Deploying 'Migrations'
> transaction hash: 0x98c7d7754cf11b2ba5a8aa676b1299720bca0668b00b91b9d223c059f5456144
> Blocks: 1 Seconds: 4
> contract address: 0x8CdaF0CD259887258Bc13a92C0a6dA92698644C0
> block number: 154
> block timestamp: 0x60f7ca69
> account: 0x627306090abaB3A6e1400e9345bC60c78a8BEf57
> balance: 90000
> gas used: 221555 (0x36173)
> gas price: 0 gwei
> value sent: 0 ETH
> total cost: 0 ETH

> Saving migration to chain.
> Saving artifacts
> Total cost: 0 ETH


Deploying 'Adoption'
> transaction hash: 0xc38e10fd2078f331d6e0f8cf27f958fad8a8a02c9789680da53f39806e407332
> Blocks: 0 Seconds: 4
> contract address: 0x345cA3e014Aaf5dcA488057592ee47305D9B3e10
> block number: 156
> block timestamp: 0x60f7ca73
> account: 0x627306090abaB3A6e1400e9345bC60c78a8BEf57
> balance: 90000
> gas used: 239851 (0x3a8eb)
> gas price: 0 gwei
> value sent: 0 ETH
> total cost: 0 ETH

> Saving migration to chain.
> Saving artifacts
> Total cost: 0 ETH

> Total deployments: 2
> Final cost: 0 ETH

Using network 'quickstartWallet'.

Compiling your contracts...
> Everything is up to date, there is nothing to compile.

Using network 'quickstartWallet'.

Compiling your contracts...
> Compiling ./test/TestAdoption.sol

✓ testUserCanAdoptPet (2071ms)
✓ testGetAdopterAddressByPetId (6070ms)
✓ testGetAdopterAddressByPetIdInArray (6077ms)

3 passing (37s)

After these tests are successful, the script builds a container for the Pet Shop dapp and deploys it, binding it to port 3001 on your system.

Sending build context to Docker daemon  411.5MB
Step 1/5 : FROM node:12.14.1-stretch-slim
12.14.1-stretch-slim: Pulling from library/node
619014d83c02: Pull complete
8c5d9aed65fb: Pull complete
aaabe8e9daf2: Pull complete
f7567fa7b9f3: Pull complete
a989ed5f800b: Pull complete
Digest: sha256:59ac2f2c3a0c490d8424306032f9b638f5ea83327ffaf23c66490e0026d1a000
Status: Downloaded newer image for node:12.14.1-stretch-slim
---> 2f7e25ad14ea
Step 2/5 : EXPOSE 3001
---> Running in 3c818550ed02
Removing intermediate container 3c818550ed02
---> 7839d0b263a2
Step 3/5 : WORKDIR /app
---> Running in be4c761044b5
Removing intermediate container be4c761044b5
---> 1a6e6d161952
Step 4/5 : COPY . /app
---> f33c3b13bc5d
Step 5/5 : CMD npm run dev
---> Running in f64911ca050f
Removing intermediate container f64911ca050f
---> 16d28763e27b
Successfully built 16d28763e27b
Successfully tagged quorum-dev-quickstart_pet_shop:latest

In the browser where you have MetaMask enabled and one of the test accounts loaded, open a new tab and navigate to the Pet Shop dapp where you can adopt lovely pets (sorry, not for real, it's a demo).

When you select Adopt, a MetaMask window pops up and requests your permission to continue with the transaction.

After the transaction is complete and successful, the status of the pet you adopted shows Success.

Dapp UI

You can also search for the transaction and view its details in the Block Explorer.

Dapp UI

The MetaMask UI also keeps a record of the transaction.

Dapp UI

Deploy your own dapp

You can deploy your own dapp to the Quorum Developer Quickstart by configuring your dapp to point to the Quickstart network.

If you're using Truffle, update the networks object in the Truffle configuration file to specify which networks to connect to for deployments and testing. The Quickstart's RPC service endpoint is http://localhost:8545.

For example, the following is the Truffle configuration file for the Pet Shop dapp used in the Quickstart GoQuorum network:

const PrivateKeyProvider = require("@truffle/hdwallet-provider");

// insert the private key of the account used in MetaMask, e.g. Account 1 (Miner Coinbase Account)
const privateKey =

module.exports = {
networks: {
development: {
host: "",
port: 7545,
network_id: "*", // Match any network id
develop: {
port: 8545,
quickstartWallet: {
provider: () =>
new PrivateKeyProvider(privateKey, "http://localhost:8545"),
network_id: "*",
type: "quorum",
gasPrice: 0,
chainId: 1337,

Deploy the dapp using:

truffle migrate --network quickstartWallet

Stop and restart the private network without removing containers

To shut down the private network without deleting the containers:


This command stops the containers related to the services specified in the docker-compose.yml file.

To restart the private network:

Stop the private network and remove containers

To shut down the private network and delete all containers and images created from running the sample network and the Pet Shop dapp:


Add a new node to the network

New nodes joining an existing network require the following:

  • The same genesis file used by all other nodes on the running network.
  • A list of nodes to connect to; this is done by specifying bootnodes, or by providing a list of static nodes.
  • A node key pair and optionally an account. If the running network is using permissions, then you need to add the new node's enode details to the permissions file used by existing nodes, or update the onchain permissioning contract.

The following steps describe the process to add a new node to the Quorum Dev Quickstart.

1. Create the node key files

Create a node key pair and account for a new node, by running the following script:

cd ./extra
npm install
node generate_node_keys.js --password "Password"

The --password parameter is optional.

2. Create new node directory

In the config/nodes directory, create a subdirectory for the new node (for example, newnode), and move the nodekey,, address and accountkey files from the previous step into this directory.

3. Update docker-compose

Add an entry for the new node into the docker-compose file:

<< : *quorum-def
container_name: newnode
- 18545:8545/tcp
- 18546:8546/tcp
- 30303
- 9545
- ./config/goquorum:/quorum
- ./config/nodes/newnode:/config/keys
- ./logs/quorum:/var/log/quorum/

Select an IP address and port map that aren't being used for the other containers. Additionally mount the newly created folder ./config/nodes/newnode to the /config/keys directory of the new node.

4. Update files with the enode address

Add the new node's enode address to the static nodes file and permissions file.

The enode uses the format enode://pubkey@ip_address:30303?discport=0&raftport=53000. where raftport is only required for the Raft consensus algorithm

If the is 4540ea...9c1d78 and the IP address is, then the enode address would be "enode://4540ea...9c1d78@", which must be added to both files.

5. Start the network

Once complete, start the network up with ./

On a live network the process is the same when using local permissions with the permissioned-nodes.json file. You don't need to restart the network and subsequent changes to the files are picked up by the servers.

When using the smart contract you can either make changes via a dapp or via RPC API calls.