Start Developing Hyperledger Fabric Chaincode in Node.js
By: Salman Dabbakuti
Tags: Chaincode, Nodejs, Hyperledger Fabric, Blockchain, Hyperledger

Photo by Negative space at pexels.com

What is Chaincode in Hyperledger Fabric terms??

In Hyperledger Fabric, Chaincode is the piece of code that runs on top of the network peers to implement the business logic of how applications interact with the ledger. When a transaction is proposed, it triggers chaincode that decides what state change should be applied to the ledger. So, in order to develop decentralized applications on Hyperledger Fabric, one must write their application logic called as Chaincode.

Chaincode can be written in Go, Nodejs, Java. Compared to the other two languages, Node.js is a language that more people understand and find easier to use including me. but, still writing and deploying Node.js Chaincode is little tricky due to very less information available in its documentation. So, I decided to write some concepts of Node.js Chaincode and step by step approach to write and deploying simple Chaincode.

Hyperledger Fabric Database

Before going deep into Chaincode, first, let’s have a look at where data is stored in Hyperledger Fabric Network. Hyperledger Fabric uses a key-value database to store its state. By default, fabric uses a LevelDB. This DB holds binary data of Particular object which can be queried using its key. Unlike traditional database being central, Blockchain Database is on every Peer of the network. hence, it is called Decentralized Network.

Apart from LevelDB, There is another database used for Hyperledger Fabric Called CouchDB. CouchDB is an optional alternative external pluggable state database. Like the LevelDB key-value store, CouchDB can store any binary data that is modeled in chaincode. But as a JSON document store, CouchDB additionally enables rich query against the chaincode data, when chaincode values (e.g. assets) are modeled as JSON data.

Chaincode Components

1.fabric-contract-api :

A high-level contract API for implementing smart contracts (available as npm module)

2.fabric-shim:

A low-level contract API for implementing smart contracts (available as npm module)

We can think of fabric-shim as a decremental version of fabric-contract-api. It's a good practice to use high-level API for new versions of fabric. However, fabric-contract-api can do everything that shim can do. Of course more than that.

3.stub:

It is an interface in fabric-contract-api and used to access and modify ledger (Database States). So, it is the main chaincode interface to read and write data on to the ledger. how can we read and write data?? Let’s have a look at some of the common methods in Stub interface

Common Methods in stub interface

1.getState(k):

As we already know, Hyperledger Fabric Databases store data in the form of a key-value pair. This method reads the data from the ledger. It takes input “k” as key and returns the binary value associated with key “k”.

2.putState(k,v):

This method writes data on the ledger. it takes “k” as key and “v” as value. To make it clear, Suppose if we wanted to store Alice’s age on to the ledger, we can take Alice as key and age as value.

3.deleteState(k):

This method deletes the value of associated key “k” from the ledger.

4.getStateByRange(k1, k9):

This method returns a range iterator over a set of keys in the ledger. It will iterate over startKey( k1) and endKey(k9) and return all key values in between these two keys.it is similar to for loops in javascript. Suppose if we have stored some user data in an order of keys k1,k2,k3…k99, we can simply get states of all these values with this method.

5.getTxID() :

This method returns the transaction Id of invoked transaction. Transaction Id is unique for every transaction on-chain. So transaction Id plays a vital role in tracking transactions.

6.getTxTimestamp():

This method returns the timestamp when the transaction was created. This is taken from the transaction channel-header, therefore it will indicate the client’s timestamp, and will have the same value across all endorsers.

Writing your First Chaincode

You have learned some pre-requisites for writing chaincode in node.js. So, for now, you might be very excited to write your first chaincode or at least I was when I learned these.

Since we are going to write Chaincode in Nodejs, we first need to create traditional npm things like package.json and index.js. if you’re not aware of this package.json.

a package.json

lists the packages your project depends on

specifies versions of a package that your project can use using semantic versioning rules

makes your build reproducible, and therefore easier to share with other developers

Briefly, our chaincode depends on fabric-contract-api and fabric-shim modules. We are mentioning these packages and versions in package.json.

We are also adding fabric-chaincode-node start as our start script which required for installing chaincode on peers.

Here is our package.json:



{

"name": "Test-Chaincode",

"version": "1.0.0",

"description": "my first exciting node.js chaincode on Hyperledger-fabric",

"main": "index.js",

"engines": {

"node": ">=8",

"npm": ">=5"

},

"scripts": {

"lint": "eslint .",

"pretest": "npm run lint",

"test": "nyc mocha --recursive",

"start": "fabric-chaincode-node start"

},

"engineStrict": true,

"author": "Hyperledger",

"license": "Apache-2.0",

"dependencies": {

"fabric-contract-api": "~1.4.0",

"fabric-shim": "~1.4.0"

},

"devDependencies": {

"chai": "^4.1.2",

"eslint": "^4.19.1",

"mocha": "^5.2.0",

"nyc": "^12.0.2",

"sinon": "^6.0.0",

"sinon-chai": "^3.2.0"

},

"nyc": {

"exclude": [

"coverage/**",

"test/**"

],

"reporter": [

"text-summary",

"html"

],

"all": true,

"check-coverage": true,

"statements": 100,

"branches": 100,

"functions": 100,

"lines": 100

}

}

If you could observe the code carefully, there is a line “main”: “index.js”. What does that mean is — on start(during installation of chaincode), npm module used to check index.js and install mentioned contract on peers.” So our index.js contains contract exported as a module.

Here is our index.js file:

'use strict';

const testContract = require(’./logic’);

module.exports.contracts = [ testContract ];

The Contract:

What is our business logic??

Adding, Retrieving and Deleting student marks.

1. Adding Marks involves writing data to the ledger. So we will use putState(k,v) method from chaincode stub interface.

2. Retrieving marks involves in reading data from the ledger. So we need to use getState(k) method.

3. Deleting marks involves deleting data. So we need to use deleteState(k) method.

The chaincode starts by bringing in scope key class Contract from the fabric-contract-api module. This class will be used to write logic. All the chaincode functions should use This library class.

const { Contract}=require(’fabric-contract-api’);

class testContract extends Contract { //Functions go here }

Adding Marks:

We are going to create a JavaScript object to store marks of a student in each subject and store this object as a value and studentId as key. When sending data to a database via server, the data has to be a string. So, first we need to convert this marks object into string using JSON.stringify() method and apply buffer to send to the database as binary data. here it is,

async addMarks(ctx,studentId,subject1,subject2,subject3) { let marks={

subj1:subject1,

subj2:subject2,

subj3:subject3

};

await ctx.stub.putState(studentId,Buffer.from(JSON.stringify(marks))); console.log(’Student Marks added To the ledger Succesfully..’);

}

Delete Marks

async deleteMarks(ctx,studentId) {



await ctx.stub.deleteState(studentId);



console.log(’Student Marks deleted from the ledger Succesfully..’);



}

Query Student Marks:

Since , We put value in the form of buffer in previous addMarks() function. Once queried, it will return buffer. So, we need to convert buffer to string and parse into an original javascript object. here it is,

async queryMarks(ctx,studentId){



let marksAsBytes = await ctx.stub.getState(studentId);

if (!marksAsBytes || marksAsBytes.toString().length <= 0) {

throw new Error(’Student with this Id does not exist: ');

}

let marks=JSON.parse(marksAsBytes.toString());

return JSON.stringify(marks);

}

Final Contract

You can find full contract here.

In order to install and test this contract, I am going to use basic-network that contains a single peer. On this network, we are going to install a Node.js chaincode named mycc onto peer0.org1.example.com and instantiate it on the channel mychannel. Then we are able to invoke these chaincode functions. Make sure docker is installed on your setup. for the sake of simplicity, I already mounted our chaincode files(logic.js, index.js, package.json) in chaincode/newcc directory. Full code can be found here

First, we need to start the network and create a channel.

git clone https://github.com/Salmandabbakuti/chaincode-essentials.git cd chaincode-essentials/basic-network ./start.sh

Wait for a few moments. It will take some time to set up network. If you encounter any permission errors, simply run under root user privileges. Once our network with a single peer is up and running, we are able to install chaincode.

For installing and invoking chaincode, we can use CLI container of Peer. Enter into CLI Container

docker exec -it cli bash

Installing and Instantiating Chaincode

peer chaincode install -n mycc -v 1.0 -p "/opt/gopath/src/github.com/newcc" -l "node" peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n mycc -l "node" -v 1.0 -c '{"Args":[]}'

Adding Marks Of Student

peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n mycc -c '{"function":"addMarks","Args":["Alice","68","84","89"]}'

Querying Marks of Student “Alice”

peer chaincode query -o orderer.example.com:7050 -C mychannel -n mycc -c '{"function":"queryMarks","Args":["Alice"]}'

Deleting Marks of “Alice” from Ledger

peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n mycc -c '{"function":"deleteMarks","Args":["Alice"]}'

above script will delete key “Alice” and associated data from the ledger. if you query marks of Alice again, you’ll get an error saying “studentId doesn’t exist.”

I also made an automated script for installing and testing this chaincode in client directory. Follow these steps for quick demo

first, exit from CLI container and run a script in client directory



exit # exits from CLI docker container if you're in

cd ..

cd client #change your directory to client chmod a+x start.sh ./start.sh #Automated script for testing

You can also invoke chaincode functions manually using scripts defined in client/start.sh file.

Conclusion

We demonstrated here, what is Chaincode, methods in chaincode stub interface, deployment structure for chaincode and how easy it is to write chaincode and deploy on a network. I hope this article somehow helped you to get started with writing chaincode and deploying on a network.

Curious to know how we can build end-user Applications on Hyperldger Fabric? Check this: Walkthrough of Hyperledger Fabric Node SDK and Client Application

References:

Hyperledger Fabric Shim API:

https://fabric-shim.github.io/release-1.4/index.html

2. Hyperledger Fabric Node SDK:

https://fabric-sdk-node.github.io/master/index.html

3. Chaincode Tutorials

https://hyperledger-fabric.readthedocs.io/en/release-1.4/chaincode.html