HomeBlockchainBlockchain DIYUsing API Server for Hyperledger Fabric Network

Using API Server for Hyperledger Fabric Network

Introduction

My previous work on implementation of API Server was done on Fabric v1.4 one year ago. Certain changes have been seen since v2.0. Here is a rework on the implementation. I will skip most of the explanation as the overall implementation logic is the same as the previous one. Only the difference is highlighted in this article.

Implementation Setup

Here is the setup of our implementation. The overall design remains the same. The only difference in this setup is the use of Test Network instead of First Network. Many sample applications and scripts are rewritten to use Test Network, and so is fabcar. For those who wish to know more about the difference between Test Network and First Network, you can refer to these articles.

Similar to our previous setup, we have two hosts setup for this implementation. They are AWS EC2 instances.

API Server Code in JavaScript

Here I simply reuse the previous apiserver.js with modification to reflect the difference in using Test Network and some change in SDK v2.2.

One major difference is the way to create wallet objects. In v1.4 it was handled by FileSystemWallet class. In v2.2 it is the class Wallets handling wallet in client applications. You can notice the change in the code (line 20, 65, 109 and 152).

Besides, the client applications in fabcar/javascript/ are modified. In v1.4 it was user1 as a directory, and inside the directory we see the private key, public key and certificates. Now In v2.2 it is appUser.id, which is an object file containing the private key and certificate. In terms of functionality they are the same. Our API server code is updated to reflect this change.

Here is the code.

const express = require('express');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

// Setting for Hyperledger Fabric
const { Wallets, Gateway } = require('fabric-network');
const fs = require('fs');
const path = require('path');
const ccpPath = path.resolve(__dirname, '.',  'connection-org1.json');


app.get('/api/queryallcars', async function (req, res) {
    try {

        let ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
        // Create a new file system based wallet for managing identities.
        const walletPath = path.join(process.cwd(), 'wallet');
        const wallet = await Wallets.newFileSystemWallet(walletPath);
        console.log(`Wallet path: ${walletPath}`);

        // Check to see if we've already enrolled the user.
        const identity = await wallet.get('appUser');
        if (!identity) {
            console.log('An identity for the user "appUser" 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(ccp, { wallet, identity: 'appUser', discovery: { enabled: true, asLocalhost: false } });

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

        // Disconnect from the gateway.
        await gateway.disconnect();

    } 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 {

        let ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
        // Create a new file system based wallet for managing identities.
        const walletPath = path.join(process.cwd(), 'wallet');
        const wallet = await Wallets.newFileSystemWallet(walletPath);
        console.log(`Wallet path: ${walletPath}`);

        // Check to see if we've already enrolled the user.
        const identity = await wallet.get('appUser');
        if (!identity) {
            console.log('An identity for the user "appUser" 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(ccp, { wallet, identity: 'appUser', discovery: { enabled: true, asLocalhost: false } });

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

        // Disconnect from the gateway.
        await gateway.disconnect();

    } 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 {

        let ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
        // Create a new file system based wallet for managing identities.
        const walletPath = path.join(process.cwd(), 'wallet');
        const wallet = await Wallets.newFileSystemWallet(walletPath);
        console.log(`Wallet path: ${walletPath}`);

        // Check to see if we've already enrolled the user.
        const identity = await wallet.get('appUser');
        if (!identity) {
            console.log('An identity for the user "appUser" 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(ccp, { wallet, identity: 'appUser', discovery: { enabled: true, asLocalhost: false } });

        // 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 {

        let ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
        // Create a new file system based wallet for managing identities.
        const walletPath = path.join(process.cwd(), 'wallet');
        const wallet = await Wallets.newFileSystemWallet(walletPath);
        console.log(`Wallet path: ${walletPath}`);

        // Check to see if we've already enrolled the user.
        const identity = await wallet.get('appUser');
        if (!identity) {
            console.log('An identity for the user "appUser" 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(ccp, { wallet, identity: 'appUser', discovery: { enabled: true, asLocalhost: false } });

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

app.listen(8080);

Demonstration Step

On Fabric Host

(screenshots are in black colour background)

Step 1: Bring up Test Network and deploy fabcar chaincode

We use the script to perform this step.

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

Step 2: Generate user appUser for API server

By default, the fabcar uses SDK to generate a user appUser for client application (API server). we first install the packages. Then we execute the two scripts enrollAdmin.js and registerUser.js.

Using API Server for Hyperledger Fabric Network 2

Upon success we will see the wallet/ directory and appUser.id is generated.

Using API Server for Hyperledger Fabric Network 3

For a quick test, we can use appUser to perform a chaincode function for querying all cars. We run node query.js, which performs the queryAllCars function. We will see the ten car records preloaded with initLedger function done in the script.

Using API Server for Hyperledger Fabric Network 4

On API Server Host

(screenshots are in blue colour background)

Now we move to API Server. We use a Ubuntu 18.04 LTS server.

Step 3: Install NodeJS environment

sudo apt-get update
sudo apt install curl
curl -sL https://deb.nodesource.com/setup_10.x | sudo bash -
sudo apt install -y nodejs
sudo apt-get install build-essentialnode -v
npm -v

Step 4: Copy files from Fabric Host

Create the directory in API Server Host.

mkdir apiserver
cd apiserver
Using API Server for Hyperledger Fabric Network 6

The files we need on API Server are

  • wallet (identity): fabcar/javascript/wallet/appUser.id
  • node package file: fabcar/javascript/package.json
  • connection profile: test-network/organizations/peerOrganizations/org1.example.com/connection-org1.json

Again we are using localhost as a bridge for my two AWS EC2 instances. For well structure we move the appUser.id to wallet/appUser.id

Here is the result.

Step 5: Copy apiserver.js to this directory.

Step 6: Modify connection profile

The connection profile when generated is using localhost. We change it from localhost to IP of Fabric Host (change to yours).

sed -i 's/localhost/34.203.38.3/g' connection-org1.json

Step 7: Update /etc/hosts to reach Fabric Host

Finally we let API Server Host reach Fabric Host by resource names we are using. Here is the result.

Using API Server for Hyperledger Fabric Network 9

Now configuration is ready.

Step 8: Install packages on API Server Host

npm install
npm install express body-parser --save

Step 9: Run API Server

node apiserver.js

As we have kept the console.log in our code, we will see console.log result in apiserver.js when accessing the APIs.

Testing: on my localhost

(screenshots are in red colour background)

Now the API Server is exposing the API we can access the fabcar application. We follow the same test suite in our previous articles.

Query All Cars

curl 
Using API Server for Hyperledger Fabric Network 10

Add a new car and query the new car

curl -d '{"carid":"CAR12","make":"Honda","model":"Accord","colour":"black","owner":"Tom"}' -H "Content-Type: application/json" -X POST curl 
Using API Server for Hyperledger Fabric Network 11

Change owner for a car and query that car again

curl curl -d '{"owner":"KC"}' -H "Content-Type: application/json" -X PUT curl 
Using API Server for Hyperledger Fabric Network 12

The API Server is working well.

Source link

Most Popular