Deep-Dive into Fabcar (revised)

KC Tam
23 min readAug 29, 2019

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.

While the official document provides very detail discussion on both parts, this article plans to give some illustration and explanation on this example. We first depict the application itself as a whole picture, and then deep dive into each part of this whole picture, one by one. And from this I hope readers have a better understanding on how a typical business blockchain application is built.

This is a quite heavy article with the following sessions.

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

The code used here is on Hyperledger Fabric release 1.4.2.

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.

The data are stored in the world state database inside the ledger. Interaction with the data is through chaincode. Fabcar comes with a Chaincode, containing the functions which can interact with the stored data in the ledger. They are for database (ledger) initiation, query and update. The world state is queried or updated only through the chaincode functions, and any update is logged in the blockchain inside the ledger as tamper-resistant record.

So far things mentioned above happen inside the fabric network. Application is useful only when it is accessible to external world. The external part is Client Application. Client application interacts with the fabric network and the deployed chaincodes through Software Development Kit (SDK). Hyperledger Fabric currently provides Java and Node SDK with official support, while those for other programming frameworks like Go and Python are also available.

Client application is coded to match the functions defined in chaincode. Here in the Fabcar come four sets of JavaScript codes that run in Node, for user enrollment and chaincode interaction.

As introduced in my previous work (link), the application portion (Client Application and Chaincode) is independent from the infrastructure (Fabric network). Theoretically the application can be deployed in any fabric network deployment, be it of one single organization or a complicated setup with many. We do not code any “infrastructure” information on the Chaincode part. We just specify when we install and instantiate Chaincode about the channel name of the infrastructure. On Client Application side, we also have to specify the Peer, channel ID (name) and chaincode ID (name) in the code in order to speak to the correct deployed chaincode in the fabric network. We will see it when we talk about the Chaincode and Client Application in coming sections.

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.

I have other articles describing how First Network looks like. You can make a quick reference here.

In general, the infrastructure part of First Network comes with

  • Two organizations, Org1 and Org2, each coming with two peer nodes (peer0 and peer1). Therefore in the First Network we have total four peer nodes
  • 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

All components are deployed as containers, and all are running on a host.

This is how a First Network looks like.

Bring-up and tear-down of First Network can be done through the well-scripted file first-network/byfn.sh. Before we go into this script and bring up the First Network, let’s take a look on the Fabcar chaincode.

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.

The chaincode is in fabric-samples/chaincode/fabcar/. Fabcar chaincode comes with various coding languages and here we are examining that one written in Go. The chaincode follows a specific pattern required and defined in Hyperledger Fabric. In this article we only focus on portions of our interests, which are the data structure, the Init() and Invoke() functions, plus some functions that will be called by Invoke(). The name of these functions begins with lower case.

Data Structure

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

Init()

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

We can set as many initial states as possible with this Init(). But as good practice, we usually leave this empty, and invoke another function later externally. The function we later invoke in this chaincode is initLedger().

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.

From the code we learn that, when chaincode is invoked, an argument list is given. See Line 68. The first argument is always the function (action of this invoke), the args is an optional argument list for that action.

There are total five functions defined, namely

  • queryCar
  • initLedger
  • createCar
  • queryAllCars
  • changeCarOwner

The name is intuitive enough to tell what action will be performed after calling this functions. We will provide a summary on each of them.

initLedger()

initLedger() is to preload the 10 sets of car data into the ledger. Here we can see the data is “stringified” before putting into the ledger as value with key CARx (line 113). The API to update the ledger is PutState().

This function does not require additional arguments, and should only be executed once. It does not make sense to repeat running it. When we design Client Application we shall not include any action to this function.

queryAllCars()

queryAllCars() simply iterates the record stored in the ledger and put them into formatted result. No argument is needed for this function. The API for data retrieval from the ledger is GetStateByRange() (line 140).

queryCar()

queryCar() allows query on individual car based on CarID.

One argument is required as expected, the CarID, which is used for data retrieval from ledger using GetState() (line 91). The result is returned.

createCar()

createCar() is for adding new car record into the ledger.

As it is a new record, all information is expected. A list of 5 arguments matches the required field: CarID, Maker, Model, Colour and Owner. The data is stringified and placed into ledger using PutState() (line 130).

changeCarOwner()

changeCarOwner() can change the owner of a car specified by CarID.

Two arguments are expected, the CarID and the Owner. This update is done through PutState() (line 191).

This is what the section is about. We omit other structural requirement on Hyperledger Fabric chaincode, but the functions above are good enough for us to move forward to do some chaincode operation demonstration.

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.

Step 1: Call first-network/byfn.sh to bring up First Network.

startFabric.sh calls first-network/byfn.sh to bring up First Network

Here we see some options are used.

  • -a bring up the Fabric CA for both organizations.
  • -n do not run the example chaincode
  • -s couchdb bring up couchdb for each peer node

Note that first-network/byfn.sh is well scripted such that these options are well addressed. It will call various predefined docker-compose files accordingly.

After successful execution, we will have

  • all the docker containers required in First Network setup according to the options
  • mychannel is up, and all four peer nodes have joint mychannel
  • anchoring peers are updated

Step 2: Install Fabcar chaincode in all Peer Nodes

Fabcar chaincode is installed on the participating peer nodes. In line 56–107 the chaincodes are installed in four peer nodes. Note that the chaincode is installed through cli container with proper environment variables. Here is just the code for peer0.org1.example.com. You can see it’s repeated in other peer nodes.

Chaincode installation: only peer0.org1.example.com is shown. Other nodes look similar.

Step 3: Instantiate Fabcar chaincode on mychannel

After installation the Fabcar chaincode is good for instantiation. By instantiation we specify the channel mychannel this chaincode is instantiated, the chaincode, and the endorsing policy when chaincode functions are invoked later. Here the endorsing policy requires a member of both Org1 and Org2.

Chaincode instantiation on mychannel with endorsing policy

Step 4: Invoke initLedger()

As mentioned above we will invoke initLedger() in order to preload 10 car records into our ledger. Note that as we need endorsement from both organizations, initLedger() is invoked with peers from the two organizations specified (Note: Here we see four peer nodes specified. In fact we only need one peer from each organization. We will test this in next session.)

Let’s take a look on the status after startFabric.sh is executed.

All peer nodes join mychannel

Fabcar chaincode is installed in all peers

Fabcar chaincode is instantiated in mychannel

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.

The default environment variables on CLI container points to peer0.org1. When we access other peers, we have to provide the relevant variables. See the following steps.

All commands are issued from CLI. To show the “consensus” across the peer nodes, for demonstration purpose, we will issue CLI commands to each the peer nodes in a round-robin fashion. It shows that we can query / invoke chaincode to each peer and we will get same result.

Here are the step for this demonstration

  1. Query All Cars in the ledger
  2. Query the specific car by CarID
  3. Add a new car with detail provided
  4. Query All Cars in the ledger again and we see a new car is in the ledger
  5. Change the owner of a car to someone else
  6. Query that car again and see the change is done

Step 1: Query all car records (from peer0.org1)

docker exec cli peer chaincode query -C mychannel -n fabcar -c '{"Args":["queryAllCars"]}'

Step 2: Query a specific car by CarID (from peer1.org1)

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"]}'

Step 3: Add a new car (from peer0.org2).

Don’t be scared with this long command. It contains

  1. environment variables pointing to peer0.org2
  2. 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"]}'

Step 4: Query all cars again (from peer1.org2). We see the new car CAR12 added to the ledger.

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"]}'

Step 5: Change owner of CAR4 (from peer0.org1).

Similarly, we will specify peer0.org1 and peer0.org2 per endorsing policy when chaincode invoke.

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"]}'

Step 6: Query CAR4 again (from peer1.org1)

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"]}'

This ends the demonstration. We have shown how to interact with an instantiated chaincode using CLI. And we also issue invoke / query command in different peer nodes and see the ledger is consistent across the fabric network. In real life we will use Client Application instead of CLI. In the next section we will examine the Fabcar Client Application.

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.

As Hyperledger Fabric is a permissioned blockchain platform, every participant must be permissioned (authorized) before one can interact with the fabric network. Of no exception, Client Application also needs to get this permission. It is done by obtaining the appropriate certificates issued by a Certificate Authority acceptable by the fabric network.

Inside the Client Application is the logic how a user is interacting with the fabric network and the chaincode. Per Hyperledger Fabric design, Client Application will interact with the Peer and the Orderer, for endorsement and for block generation respectively. The access point of Peer and Orderer needs to be specified in Client Application. Also, the channel name (Id) and chaincode name (Id) are specified when the proposal is reaching the fabric network. Remember a fabric network can support multiple channels and multiple chaincodes.

Finally, when Client Application performs query or invoke on the chaincode, make sure to supply the right function name and argument list if any. The whole application works only when these two parts, Client Application and Chaincode, work hand-in-hand.

With this, we are going to examine this in Fabcar Client Application.

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.

There are three NPM packages in Node SDK. You can find them here (https://github.com/hyperledger/fabric-sdk-node).

fabric-ca-client is for Fabric CA. Fabric CA is an optional item as some may use their own CA instead. If Fabric CA is used, this package performs the enrollment on Fabric CA.

fabric-network provides a higher level of abstraction for submission of query and transactions to the network.

When we examine the code we will see how to use these two packages.

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.

Inside the fabric-samples/fabcar/javascript/ directory there are four JavaScript codes. All of them obtain the connection information from first-network/connection-org1.json. Therefore all invoke / query are done on the nodes on org1. This is a bit different from how we run similar set using CLI. Nevertheless, if we change it to org2, we will get the same result.

We broadly divide them into two categories.

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.

enrollAdmin.js

We first use enrollAdmin.js to generate the certificate for an administrator.

Here is the logic

  1. Load the required modules from fabric-ca-client and fabric-network.
  2. Retrieve the detail about the First Network deployment. It contains the information of the access point of Fabric CA.
  3. Check whether “admin” is already inside the wallet/ directory. If so, no further action is needed.
  4. 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).
  5. The result of enrollment is key pair and certificate. The result is then stored in wallet/admin/.

registerUser.js

Then we use registerUser.js to register and enroll user1. It is the admin we created above to perform this registration. This user can later perform query and invoke.

Here is the logic

  1. Load the required modules from fabric-network.
  2. Retrieve the detail about the First Network deployment.
  3. Check whether user1 is already enrolled. If so no further action is needed.
  4. Check whether admin exists in the wallet. If admin is not yet enrolled, prompt for enrollAdmin.js and no further action is needed.
  5. Create a new gateway connecting to peer, with connection detail from First Network deployment.
  6. Register the user1 with admin created in previous part. The result is a secret.
  7. Now we can enroll the user1 with the secret.

The result of enrollment is key pair and certificate for user1. The result is then stored in wallet/user1/.

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.

query.js

There are two function defined in the chaincode using query: queryAllCars() and queryCar(). The code query.js is for these two chaincode functions.

Since query does not involve modifying the ledger content, the request can be fulfilled in the Peer by retrieving the data requested in the local world state database.

The logic of query.js is like this.

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

Here is where to specify the required chaincode function and arguments.

invoke.js

There are two function defined in the chaincode using query: createCar() and changeCarOwner(). The code invoke.js is for these two chaincode functions.

The logic of invoke.js is like this.

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

Here is where to specify the required chaincode function and arguments.

Demo of Client Application to the Deployed Chaincode

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

For simplicity, we just perform one query and one invoke. The step is like this

  1. Use enrollAdmin.js and registerUser.js to get certificate for user1
  2. Use query.js to inspect CAR5
  3. Use invoke.js to change car owner of CAR5
  4. Use query.js again to inspect CAR5, and see if the owner is correctly changed

We can run this on top of previous session, as we have not acted on CAR5 yet.

First, we need the proper modules, including the SDK. They are already configured in package.json. What we need is to use npm install.

cd fabric-samples/fabcar/javascript
npm install

Now we see all required modules under node_modules/.

Now we enroll an Administrator on Fabric CA.

Then, we use Administrator to register the User1 and enroll User1.

We only need to run these two codes once. With the user1 and the key pair, we can run the client applications. User1 can be used any time when needed.

Now we can modify the two files to reflect the function.

For query.js we modify the arguments in evaluateTransaction()

modify query.js such that it is to query CAR5

Now we can execute the Client Application query.js.

Show the record of CAR5.

We now modify the arguments in submitTransaction() in the invoke.js such that we change the owner of CAR5 to another owner.

modify invoke.js such that it is to change car owner for CAR5

We execute the Client Application invoke.js, and then use query.js to check the new owner.

The car owner of CAR5 is changed.

Now we see the owner is changed to what we specify in the invoke.js.

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.

A more common way is to build an API for those functions. API serves as a standard way when accessing services (that is, functions defined in the chaincode invoke), and ready to be integrated into other applications.

ExpressJS provides an easy way of API implementation. In this section we will first do a quick check on ExpressJS, and based on the setup we will design the API format. The purpose is to build the following API.

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

Note this is just for demonstration, and the code is not optimized. We simply leverage what we have in Fabcar and creating the ExpressJS to perform

I omit the API example from my original article. Readers can either access the original articles or search samples of ExpressJS in the net.

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/

In apiserver, we need to install two packages for the ExpressJS.

cd apiserver
npm install express body-parser --save

Now we will work on a file apiserver.js, which will be the code for the API server.

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.

Here is the structure of code. The highlighted portion is where code to be inserted.

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);

We first make reference to both query.js and invoke.js to include the “Setting for Hyperledger Fabric” portion, in which we define the required source of the deployed network.

Then we define the four API we plan to implement (two gets, one post and one put). We need async for the callback function to align the main() in query.js and invoke.js.

We can copy directly from query.js and invoke.js on the “to be filled in” portion, individually.

GET /api/queryallcars

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);
}
});

GET /api/query/CARID

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);
}
});

POST /api/addcar/

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);
}
})

PUT /api/changeowner/CARID

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.

Let clean up the fabric network and rebuild a new environment. See the last section about the clean up procedure.

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

Then we bring up the First Network for Fabcar again.

cd /fabric-samples/fabcar
./startFabric.sh

After the network is up and running, we will remove the wallet and get back the user1 certificate through registerUser.js. After that we can run our apiserver.

cd /fabric-samples/fabcar/apiserver
rm -rf wallet
node enrollAdmin.js
node registerUser.js
node apiserver.js

Keep an eye on this as we will see the result logged in console, per defined in the original query.js and invoke.js. In production we can remove them.

Since my server opens 8080 for external access, I am accessing the API server from my localhost using the public IP (18.234.121.178) of API server. Use localhost if you are using same host, or your own public IP address.

Step 1: Query all car records

curl http://18.234.121.178:8080/api/queryallcars

Step 2: Query a specific car by CarID

curl http://18.234.121.178:8080/api/query/CAR4

Step 3: Add a new car

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

Step 4: Query all cars again

curl http://18.234.121.178:8080/api/queryallcars

Step 5: Change owner of CAR4

curl -d '{"owner":"KC"}' -H "Content-Type: application/json" -X PUT http://18.234.121.178:8080/api/changeowner/CAR4

Step 6: Query CAR4 again

curl http://18.234.121.178:8080/api/query/CAR4

This ends the demonstration. The same result is well expected as what we change is just the Client Application, not the Chaincode itself.

As said, this demonstration is just showing how to incorporate the existing JavaScript code into an API server environment. For sure the code above is far from optimization, but it should be good enough to show how things work.

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)

Note that we need the final two commands as ./byfn.sh down cannot clean up the chaincode containers. Using these two docker commands will complete remove the stopped containers and unused images.

Hope this big article helps understand more how a blockchain application is built and running in a fabric network.

--

--