Deep-Dive into Fabcar (revised)

A Complete Application Example on Hyperledger Fabric

0. Why Revised

This article is a rework on one of my previous series about Fabcar, a good example in Hyperledger Fabric showing how application is developed. It is found that since Hyperledger Fabric 1.4.2, the fabric-network used in startFabric.sh is changed from Basic Network to First Network. Certain portions of original articles require updated. Instead of updating the previous articles, here I come up this updated version of the whole four-part article, and I make it into a big one.

1. Overview

Hyperledger Fabric comes with a lot of application examples, and among them Fabcar is a complete end-to-end application. A complete blockchain application contains two portions of code: the Chaincode which is deployed and executed inside a fabric network, and the Client Application is where the external world interacts with the chaincode deployed on this fabric network.

  1. Overall Picture of Fabcar
  2. First Network
  3. Understanding Fabcar Chaincode
  4. Bringing Up the First Network and Fabcar Chaincode
  5. Demonstration of Chaincode Functions using CLI
  6. Understanding Fabcar Client Application
  7. Building an API Server to Interact with Chaincode
  8. Clean Up

2. Overall Picture of Fabcar

Fabcar is a database of car records stored in the ledger of a fabric network. We can consider this as a traditional database storing the data: it is like a table, indexed with a Car Identifier (CarID), and the information of Maker, Model, Colour and Owner is recorded for this car.

3. First Network

Fabcar chaincode can run on any fabric networks. Here we follow the tutorial in official document, in which Fabcar is running on the First Network.

  • One orderer organization and one orderer node using SOLO as the ordering method
  • Optionally, each peer node runs a couchdb as a world state database.
  • Optionally, each Org1 and Org2 can come with a Certificate Authority, CA, running fabric-CA software with proper configuration
  • A Command Line Interface (cli) as a client interacting with the fabric network
Image for post
Image for post

4. Understanding Fabcar Chaincode

Let’s take a look the Fabcar chaincode. As a recap, chaincode is executed on endorsing peer nodes inside a fabric network. It is where the business logic resides and will be executed upon invoke. The ledger is updated only when chaincode is invoked.

Data Structure

As said, Fabcar is a “database” in the ledger. Here is how the data is structured.

Image for post
Image for post

Init()

This is a specific function required in Hyperledger Fabric chaincode. The Init() is executed when the chaincode is instantiated in the Fabric network.

Image for post
Image for post

Invoke()

This is another specific function required in Hyperledger Fabric chaincode. The Invoke() usually defines some further actions (functions) when the chaincode is invoked. It usually follows the pattern like this.

Image for post
Image for post
  • initLedger
  • createCar
  • queryAllCars
  • changeCarOwner
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

5. Bringing Up the First Network and Fabcar Chaincode

We will walk through the fabcar/startFabric.sh, and we see that the First Network is brought up, channel is created and joined by peers, and chaincode is deployed in the following steps.

Image for post
Image for post
startFabric.sh calls first-network/byfn.sh to bring up First Network
  • -n do not run the example chaincode
  • -s couchdb bring up couchdb for each peer node
  • mychannel is up, and all four peer nodes have joint mychannel
  • anchoring peers are updated
Image for post
Image for post
Chaincode installation: only peer0.org1.example.com is shown. Other nodes look similar.
Image for post
Image for post
Chaincode instantiation on mychannel with endorsing policy
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

6. Demonstration of Chaincode Functions using CLI

Here we begin to demonstrate how the chaincode is working, or more specifically, what happens when the chaincode is queried or invoked.

Image for post
Image for post
  1. Query the specific car by CarID
  2. Add a new car with detail provided
  3. Query All Cars in the ledger again and we see a new car is in the ledger
  4. Change the owner of a car to someone else
  5. Query that car again and see the change is done
docker exec cli peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryAllCars"]}'
Image for post
Image for post
docker exec -e CORE_PEER_ADDRESS=peer1.org1.example.com:8051 -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt cli peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryCar","CAR4"]}'
Image for post
Image for post
  1. as endorsing policy requires two organization, we send this invoke to peer0.org1 and peer0.org2 (see peerAddresses option) plus their TLS certificates (tlsRootCertFiles option).
docker exec -it -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n fabcar --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["createCar", "CAR12", "Honda", "Accord", "black", "Tom"]}'
Image for post
Image for post
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer1.org2.example.com:10051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryAllCars"]}'
Image for post
Image for post
docker exec cli peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n fabcar --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["changeCarOwner", "CAR4", "KC"]}'
Image for post
Image for post
docker exec -e CORE_PEER_ADDRESS=peer1.org1.example.com:8051 -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt cli peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryCar","CAR4"]}'
Image for post
Image for post

7. Understanding Fabcar Client Application

Client Application in General

In Hyperledger Fabric, Client Application is the point external world interacting with the fabric network and the deployed chaincode. This interaction is done through SDK. Hyperledger Fabric offers SDK for various programming languages. Java and Node SDK are officially released.

Fabcar in JavaScript: using Node SDK

As Fabcar Client Application comes with JavaScript and TypeScript. Both are using Node SDK. Here we examine the one in JavaScript.

Fabcar Client Application

Fabcar Client Application is inside fabric-samples/fabcar/ directory. The latest release 1.4 the Fabcar codes are reworked in JavaScript. We see three folders: javascript/, typescript/ and javascript-low-level/. The demonstration here is using javascript/. If you are interested in pre 1.4 code, you can use javascript-low-level/. If you compare them you can see the large improvement on JavaScript coding.

Image for post
Image for post

User Enrollment into Fabric Network

The two codes enrollAdmin.js and registerUser.js are responsible for admin and user enrollment on the Fabric network before we can interact the chaincode. The result is key pair and certificates for admin and for user. In 1.4 they are stored in wallet/ directory locally.

  1. Retrieve the detail about the First Network deployment. It contains the information of the access point of Fabric CA.
  2. Check whether “admin” is already inside the wallet/ directory. If so, no further action is needed.
  3. Enroll the admin user to the Fabric CA with enrollment ID and Secret (aligned with what is defined in Fabric CA, see ca0 service in the docker-compose file first-network/docker-composer-ca.yaml).
  4. The result of enrollment is key pair and certificate. The result is then stored in wallet/admin/.
  1. Retrieve the detail about the First Network deployment.
  2. Check whether user1 is already enrolled. If so no further action is needed.
  3. Check whether admin exists in the wallet. If admin is not yet enrolled, prompt for enrollAdmin.js and no further action is needed.
  4. Create a new gateway connecting to peer, with connection detail from First Network deployment.
  5. Register the user1 with admin created in previous part. The result is a secret.
  6. Now we can enroll the user1 with the secret.

Chaincode invoking

The fabric-samples come with two JavaScript file: query.js and invoke.js. From the name we know that they are for the two category of chaincode invoke.

  1. Retrieve the detail about the First Network deployment.
  2. Check whether user1 is already enrolled (in wallet). If not, prompt for registerUser.js and no further action is needed.
  3. Create a new gateway connecting to peer, with connection detail from First Network deployment.
  4. Use the gateway to retrieve the channel (network) mychannel and chaincode (contract) fabcar.
  5. Use contract API evaluateTransaction() with the arguments required for query.
  6. The result is shown in string.
Image for post
Image for post
  1. Retrieve the detail about the First Network deployment.
  2. Check whether user1 is already enrolled (in wallet). If not, prompt for registerUser.js and no further action is needed.
  3. Create a new gateway connecting to peer, with connection detail from First Network deployment.
  4. Use the gateway to retrieve the channel (network) mychannel and chaincode (contract) fabcar.
  5. Use contract API submitTransaction() with the arguments required for invoke.
  6. Disconnect the gateway once transaction is processed.
Image for post
Image for post

Demo of Client Application to the Deployed Chaincode

Finally, we will use the Client Application to interact with the chaincode.

  1. Use query.js to inspect CAR5
  2. Use invoke.js to change car owner of CAR5
  3. Use query.js again to inspect CAR5, and see if the owner is correctly changed
cd fabric-samples/fabcar/javascript
npm install
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
modify query.js such that it is to query CAR5
Image for post
Image for post
Show the record of CAR5.
Image for post
Image for post
modify invoke.js such that it is to change car owner for CAR5
Image for post
Image for post
The car owner of CAR5 is changed.

8. Building an API Server to Interact with Chaincode

So far we have followed what comes with the fabric-samples: the Chaincode and the Client Application of Fabcar. In real life, the Client Application should be more comprehensive. For example, instead of “hardcode” the function and arguments, it is better if we can specify it during executing the Client Application. For example, as an argument list when issuing node commands.

Image for post
Image for post
  • GET /api/query/CarID
  • POST /api/addcar/ with detail in body
  • PUT /api/changeowner/CarID with new owner in body

Prepare the API Project

To make things clean, we replicate the javascript/ directory and make one apiserver/. Note that the previous modules installed in javascript/ are also copied to apiserver/. We do not need to install them again.

cd fabric-samples/fabcar/
cp -r javascript/ apiserver/
cd apiserver
npm install express body-parser --save

API Server for Client Application

We build this apiserver.js based on the ExpressJS example in previous section, integrated with the query.js and invoke.js. We use the structure of task list for the four API, and in each of them we include the required code from either query.js and invoke.js.

var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
// Setting for Hyperledger Fabric
const { FileSystemWallet, Gateway } = require('fabric-network');
const path = require('path');
const ccpPath = path.resolve(__dirname, '..', '..', 'first-network', 'connection-org1.json');
app.get('/api/queryallcars', async function (req, res) {
// to be filled in
});
app.get('/api/query/:car_index', async function (req, res) {
// to be filled in
});
app.post('/api/addcar/', async function (req, res) {
// to be filled in
})
app.put('/api/changeowner/:car_index', async function (req, res) {
// to be filled in
})
app.listen(8080);
app.get('/api/queryallcars', async function (req, res) {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('fabcar');
// Evaluate the specified transaction.
// queryCar transaction - requires 1 argument, ex: ('queryCar', 'CAR4')
// queryAllCars transaction - requires no arguments, ex: ('queryAllCars')
const result = await contract.evaluateTransaction('queryAllCars');
console.log(`Transaction has been evaluated, result is: ${result.toString()}`);
res.status(200).json({response: result.toString()});
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
res.status(500).json({error: error});
process.exit(1);
}
});
app.get('/api/query/:car_index', async function (req, res) {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('fabcar');
// Evaluate the specified transaction.
// queryCar transaction - requires 1 argument, ex: ('queryCar', 'CAR4')
// queryAllCars transaction - requires no arguments, ex: ('queryAllCars')
const result = await contract.evaluateTransaction('queryCar', req.params.car_index);
console.log(`Transaction has been evaluated, result is: ${result.toString()}`);
res.status(200).json({response: result.toString()});
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
res.status(500).json({error: error});
process.exit(1);
}
});
app.post('/api/addcar/', async function (req, res) {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('fabcar');
// Submit the specified transaction.
// createCar transaction - requires 5 argument, ex: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom')
// changeCarOwner transaction - requires 2 args , ex: ('changeCarOwner', 'CAR10', 'Dave')
await contract.submitTransaction('createCar', req.body.carid, req.body.make, req.body.model, req.body.colour, req.body.owner);
console.log('Transaction has been submitted');
res.send('Transaction has been submitted');
// Disconnect from the gateway.
await gateway.disconnect();
} catch (error) {
console.error(`Failed to submit transaction: ${error}`);
process.exit(1);
}
})
app.put('/api/changeowner/:car_index', async function (req, res) {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('fabcar');
// Submit the specified transaction.
// createCar transaction - requires 5 argument, ex: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom')
// changeCarOwner transaction - requires 2 args , ex: ('changeCarOwner', 'CAR10', 'Dave')
await contract.submitTransaction('changeCarOwner', req.params.car_index, req.body.owner);
console.log('Transaction has been submitted');
res.send('Transaction has been submitted');
// Disconnect from the gateway.
await gateway.disconnect();
} catch (error) {
console.error(`Failed to submit transaction: ${error}`);
process.exit(1);
}
})

Demonstrate the API Use

Finally we will use curl to demonstrate how to use the API. We follow the same section 6.

cd /fabric-samples/first-network
./byfn.sh down
docker rm $(docker ps -aq)
docker rmi $(docker images dev-* -q)
cd /fabric-samples/fabcar
./startFabric.sh
cd /fabric-samples/fabcar/apiserver
rm -rf wallet
node enrollAdmin.js
node registerUser.js
node apiserver.js
Image for post
Image for post
curl http://18.234.121.178:8080/api/queryallcars
Image for post
Image for post
curl http://18.234.121.178:8080/api/query/CAR4
Image for post
Image for post
curl -d '{"carid":"CAR12","make":"Honda","model":"Accord","colour":"black","owner":"Tom"}' -H "Content-Type: application/json" -X POST http://18.234.121.178:8080/api/addcar
Image for post
Image for post
curl http://18.234.121.178:8080/api/queryallcars
Image for post
Image for post
curl -d '{"owner":"KC"}' -H "Content-Type: application/json" -X PUT http://18.234.121.178:8080/api/changeowner/CAR4
Image for post
Image for post
curl http://18.234.121.178:8080/api/query/CAR4
Image for post
Image for post

9. Clean Up

We bring up the fabric network using fabcar/startFabric.sh. We do not see a “stop” or “teardown” script. You can clean up everything with the following commands.

cd /fabric-samples/first-network
./byfn.sh down
docker rm $(docker ps -aq)
docker rmi $(docker images dev-* -q)

Written by

Happy to share what I learn on blockchain. Visit http://www.ledgertech.biz/kcarticles.html for my works. or reach me on https://www.linkedin.com/in/ktam1/.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store