Public & Private Transactions On JPMorgan’s Quorum With Web3
Executing public & private transactions on JPMorgan’s Quorum with Web3
This guide is intended for anyone interested in experimenting with Quorum. It is an introduction to deploying contracts and sending both public and private Quorum transactions using the web3.js library.
This article will cover:
- Formatting your smart contract
- Setting up your network / infrastructure with Chainstack
- Quorum Public Transactions
- Quorum Private Transactions
To better illustrate the mentioned features, we will introduce a simplified use case that covers a working implementation combining IoT and blockchain to monitor the participants’ storage facility temperature.
Background
A group of storage companies has decided to form a storage consortium to share information and automate processes on the blockchain. In this case, they have decided to use Quorum. In this tutorial, we will cover two use cases: public and private transactions.
Transactions are created by different parties to interact with one another within the consortium they belong to, each transaction will either deploy a contract or execute functions within that contract to upload data to the network. These updates will then be replicated across all nodes in the consortium.
Public transactions are designed to be publicly viewable by all parties within the consortium. Private transactions, on the other hand, provide an additional layer of privacy. It enables transactions and contracts to be accessible only by organizations that have been granted permission to do so.
We will use the same smart contract for both use cases to better explain how public and private transactions work.
Smart contract
Below is a simple smart contract that I’ve created for this use case. It has a public variable temperature, which can be modified using the set method and fetched using the get method.
pragma solidity ^0.4.25;
contract TemperatureMonitor {
int8 public temperature;
function set(int8 temp) public {
temperature = temp;
}
function get() view public returns (int8) {
return temperature;
}
}
For the contract to work with web3.js, it has to be first formatted into its respective ABI and bytecode formats. Using the function below called formatContract compiles the contract using Ethereum’s solc-js compiler.
function formatContract() {
const path = './contracts/temperatureMonitor.sol';
const source = fs.readFileSync(path,'UTF8');
return solc.compile(source, 1).contracts[':TemperatureMonitor'];
}
The formatted contract should look something like this:
// interface
[
{
constant: true,
inputs: [],
name: 'get',
outputs: [Array],
payable: false,
stateMutability: 'view',
type: 'function'
},
{
constant: true,
inputs: [],
name: 'temperature',
outputs: [Array],
payable: false,
stateMutability: 'view',
type: 'function'
},
{
constant: false,
inputs: [Array],
name: 'set',
outputs: [],
payable: false,
stateMutability: 'nonpayable',
type: 'function'
}
]
0x608060405234801561001057600080fd5b50610104806100206000396000f30060
806040526004361060525763ffffffff7c0100000000000000000000000000000000
0000000000000000000000006000350416636d4ce63c81146057578063adccea1214
6082578063faee13b9146094575b600080fd5b348015606257600080fd5b50606960
ae565b60408051600092830b90920b8252519081900360200190f35b348015608d57
600080fd5b50606960b7565b348015609f57600080fd5b5060ac60043560000b60c0
565b005b60008054900b90565b60008054900b81565b6000805491810b60ff1660ff
199092169190911790555600a165627a7a72305820af0086d55a9a4e6d52cb6b3967
afd764ca89df91b2f42d7bf3b30098d222e5c50029
Now that the contract is formatted and ready, we will move on to set up the blockchain infrastructure to deploy the contract.
Deploying the Quorum nodes
Deploying blockchain nodes requires deep technical expertise, especially when it comes to synchronizing nodes within a network through the command line interface (CLI). I believe that most people have had a tough time setting up, maintaining, or troubleshooting their own blockchain networks.
Manual deployment is tedious for anyone who does not have the experience, interest, or the time to execute a long list of dependencies and protocol configurations. For such developers, I highly recommend using a blockchain-platform-as-a-service that simplifies the setup and maintenance of their blockchain.
The Quorum explorer feature on Chainstack provides a better view of how blockchain and smart contracts work. Below are screenshots on how I used Chainstack to set up the Quorum Raft network with 3 nodes for this use case.
Public transactions
Background: Localized temperatures are a tremendous influence in cutting costs for heat-sensitive storage facilities. By enabling companies to share the ambient temperature of their geographical locations in real time and recording them on an immutable ledger, business participants can decide which area is optimal for heat-sensitive storage facilities.
We will execute 3 different tasks:
Deploying smart contract through Node1
const contractAddress = await deployContract(raft1Node); console.log(`Contract address after deployment: ${contractAddress}`);Setting the temperature on Node2. This should update the temperature to 3 degrees.
const status = await setTemperature(raft2Node, contractAddress, 3); console.log(`Transaction status: ${status}`);Node3 retrieves the temperature from the smart contract; it should return 3 degrees.
const temp = await getTemperature(raft3Node, contractAddress); console.log('Retrieved contract Temperature', temp);
Initiate web3 instance with RPC for the 3 nodes:
const raft1Node = new Web3(
new Web3.providers.HttpProvider(process.env.RPC1), null, {
transactionConfirmationBlocks: 1,
});
const raft2Node = new Web3(
new Web3.providers.HttpProvider(process.env.RPC2), null, {
transactionConfirmationBlocks: 1,
});
const raft3Node = new Web3(
new Web3.providers.HttpProvider(process.env.RPC3), null, {
transactionConfirmationBlocks: 1,
});
Next we’ll deploy the smart contract:
async function deployContract(web3) {
const address = await getAddress(web3);
const contract = new web3.eth.Contract(
temperatureMonitor.interface
);
return contract.deploy({
data: temperatureMonitor.bytecode,
})
.send({
from: address,
gas: '0x2CD29C0',
})
.on('error', console.error)
.then((newContractInstance) => {
return newContractInstance.options.address;
});
}
web3.js provides two methods to interact with the contract: call and send. We can update the contract’s temperature by executing the set method using web3’s send method.
async function setTemperature(web3, contractAddress, temp) {
const myContract = await getContract(web3, contractAddress);
return myContract.methods.set(temp).send({}).then((receipt) => {
return receipt.status;
});
}
Now we are ready to run the full public.js, which should show the following results.
// Execute public script
node public.js
Contract address after deployment: 0xf46141Ac7D6D6E986eFb2321756b5d1e8a25008F
Transaction status: true
Retrieved contract Temperature 3
Private transactions
Background: A common business requirement is secured data encryption. For example, a supermarket rents storage solutions from a vendor for storing perishables. The vendor transmits temperature readings every 30 seconds from its IoT devices exclusively for the supermarket.
We will execute 4 different tasks:
Deploying a private contract for Supermarket and Storage Facility through Supermarket:
const contractAddress = await deployContract(raft1Node, process.env.PK2); console.log(`Contract address after deployment: ${contractAddress}`);Set temperature from External Party (external node) and fetch temperature:
await setTemperature(raft3Node, contractAddress, process.env.PK1, 10); const temp = await getTemperature(raft3Node, contractAddress); console.log(`[Node3] temp retrieved after updating contract from external nodes: ${temp}`);Set temperature from Storage Facility (internal node) and fetch temperature:
await setTemperature(raft2Node, contractAddress, process.env.PK1, 12); const temp2 = await getTemperature(raft2Node, contractAddress); console.log(`[Node2] temp retrieved after updating contract from internal nodes: ${temp2}`);Fetch Temperature from External Party (external node):
const temp3 = await getTemperature(raft3Node, contractAddress); console.log(`[Node3] temp retrieved from external nodes after update: ${temp3}`);
Now we are ready to run the full private.js, which should show the following results.
node private.js
Contract address after deployment: 0x85dBF88B4dfa47e73608b33454E4e3BA2812B21D
[Node3] temp retrieved after updating contract from external nodes: null
[Node2] temp retrieved after updating contract from internal nodes: 12
[Node3] temp retrieved from external nodes after update: null
As you can see, both transactions went through, but only the transaction executed from Storage Facility managed to update the temperature on the contract. Hence, private transactions ensure the immutability of the data by internal parties without exposing the data to external observers.