Chaincode for Go developers, Part 3: Develop a client application for a blockchain network based on Hyperledger
Fabric v0.6

Chaincode for Go developers, Part 3

Develop a client application for a blockchain network based on Hyperledger
Fabric v0.6

A deep dive into the building blocks of a Node.js client app

Content series:

This content is part # of # in the series: Chaincode for Go developers, Part 3

Stay tuned for additional content in this series.

This content is part of the series:Chaincode for Go developers, Part 3

Stay tuned for additional content in this series.

With the Hyperledger Fabric Client SDK for Node.js,
you can easily use APIs to interact with a blockchain based on Hyperledger
Fabric v0.6. This tutorial shows you how to write some of the most common
and necessary functions in a client application. All code examples in this
tutorial are included in the reusable sample client that you can download and
customize to fit your needs.

In this tutorial, the conclusion of a three-part series, you’ll learn how to develop a Node.js client
application to talk to a blockchain network based on Hyperledger Fabric
v0.6. You’ll learn about registration, enrollment, and access control
through TCerts, and get code for setting up a blockchain network, a
Cloudant-based key value store, and an API layer for invoking and querying
the blockchain. By following the steps in this tutorial, you can deploy
the chaincode you developed in Part 1 onto the Blockchain service on IBM Bluemix® and invoke it from your
client application.

Prerequisites

We’ll continue with the home loan application use case introduced in the previous parts of this tutorial series.

  • This tutorial references the chaincode written in Part 1, so please review that tutorial before
    proceeding.
  • You should be comfortable with Node.js and Bluebird promises.
  • You should have Node v4.5.0 installed on your machine.
  • You should have NPM
    v2.15.9
    installed on your machine.
  • You should have Git v2.7.4
    installed on your machine.
  • You need a Bluemix account (get a free trial account).
  • You need the sample code package (download
    it
    from this tutorial).

Key terms and concepts

The Hyperledger Fabric Client SDK is the official SDK for
Hyperledger Fabric v0.6. It is written in TypeScript and has comprehensive
APIs to interact with a blockchain network based on Hyperledger Fabric via
a Node.js application. The SDK uses gRPC to talk to the blockchain
network.

  • Chain. The chain is the top level object that gets
    the ball rolling. The chain object is used to configure the peers,
    membership services, security, key value store, and the event hub. The
    chain object gives access to available Member objects, which are used
    to transact on the blockchain network.
  • Member. The member object represents an entity that
    can transact on a blockchain network. The member object is used to
    register and enroll with the membership services. Once the
    registration and enrollment are done, the member object stores the
    received certificates in the previously configured key value store.
    The member object can then be used to talk to the peer(s) on the
    blockchain network to invoke transactions and query the ledger.
  • KeyValueStore. The KeyValueStore stores member
    metadata and enrollment certificates. The SDK provides a default
    file-based implementation for the KeyValueStore that can be used in
    the client application. However, it is recommended that you write your
    own implementation for the KeyValueStore that is more secure and
    easily accessible. I’ll take you through an implementation for the
    KeyValueStore that is based on Cloudant NoSQL DB in our client.
  • Registration, enrollment, and access control. To
    interact with a blockchain network based on Hyperledger Fabric, a user
    needs to register and enroll with the Certificate Authority (CA). The
    CA generates transaction certificates, which are required for
    transacting on the blockchain network and which can also be used for
    attribute-based access control. The CA provides several different
    certificate services:

    • Enrollment Certificate Authority (ECA). The
      ECA enables new users to register with the blockchain network
      and subsequently request an enrollment certificate pair. The
      pair consists of two certificates, one for data signing and
      one for data encryption.
    • Transaction Certificate Authority (TCA) .
      Once the user is enrolled in the blockchain network, the user
      can request Transaction Certificates (TCerts). TCerts are
      required for deploying chaincode and for invoking chaincode
      transactions on the blockchain network. The TCert can also be
      embedded with encrypted user-defined attributes. These
      attributes can then be accessed in the chaincode for access
      control. A best practice to ensure privacy is to a different
      TCert for every transaction. This is the default behavior of
      the SDK.
    • TLS Certificate Authority. TLS certificates
      secure communication channels between the client, peers, and
      the CA. The TLS certificates can be requested from the TLS
      certificate authority (TCA).
    • Attribute Certificate Authority (ACA). The
      ACA stores attributes for each user and affiliation in a
      database. It certifies whether a particular user has ownership
      of the attributes. The ACA returns an ACert for validated
      attributes, with the attribute values encrypted.

    This diagram shows the interaction flow between the user,
    Enrollment Certificate Authority (ECA), Transaction Certificate
    Authority (TCA), and Attribute Certificate Authority (ACA).

    In the diagram above:

    1. The user makes a registration request to the ECA and passes in
      an enrollment ID, role, affiliation, and attribute key value
      pairs, among others.
    2. If the enrollment ID passed in is not already registered, the
      ECA responds to the user with a one-time password.
    3. The user makes an enrollment request to the ECA and passes in
      a signing and encryption key along with the one-time password
      token obtained in the previous step.
    4. Upon verification, the ECA returns an enrollment certificate
      pair. This pair consists of two certificates, one for signing
      and one for encryption. This pair along with other user
      metadata is then stored in the key value store configured in
      the chain object.
    5. The ECA makes a fetch attributes request to the ACA, passing
      in the ECert generated in step 4.
    6. The ACA stores the ECert along with details such as
      affiliation, attribute key/value pairs, and their validity in
      its database.
    7. The user makes a request for TCerts to the TCA, which can be
      used for invoking the chaincode. The user passes in the ECert,
      the number of TCerts to be generated, and a list of
      attributes, which can be a subset of the list of attributes
      passed in step 1.
    8. The TCA makes a request to the ACA to ensure the user
      possesses the said attributes. The TCA passes in the ECert
      along with the list of attributes.
    9. The ACA looks up the attributes in its database.
    10. If no attributes are found, an error is returned. If all or
      some of the attributes are found, then an ACert with the found
      attributes are returned to the TCA.
    11. The TCA creates a batch of TCerts and returns them to the
      user. Each TCert contains the valid attributes.

Get started

The code samples in this tutorial and available for download were developed and tested with the
following software and versions:

Create the IBM Blockchain service on
Bluemix

To develop the sample client in this tutorial, you need a Bluemix account
(get a Bluemix free trial account).

  1. When you have your Bluemix account, create the Blockchain service on Bluemix. Be sure to select
    the Starter Developer plan (beta), which will set up
    a blockchain network for you based on Hyperledger Fabric v0.6.
  2. When you have created the service, navigate to your Blockchain service
    homepage and click Service credentials in the
    navigation:

  3. Click View credentials under ACTIONS:

    You can now see the credentials for your service. Copy the entire
    contents in the text box and store them in a safe place. These
    credentials contain the information necessary for a client
    application to talk to the blockchain network. Later sections in
    this tutorial will explain how to put these service
    credentials
    into the configuration file for your sample
    client.

Create the Cloudant NoSQL DB service on
Bluemix

The sample client uses my custom implementation of a Cloudant-based key
value store. The Cloudant service is a NoSQL DB service powered by couch
DB. So, as with the Blockchain service, you’ll use your Bluemix account
(get a Bluemix free trial account).

  1. When you have your Bluemix account, create the Cloudant service. Select the Lite (free)
    plan
    .
  2. When you have created the service, navigate to your Cloudant service
    homepage and click Service credentials in the
    navigation.
  3. Click View credentials under ACTIONS.

You can now see the credentials for your service. Copy the entire contents
in the text box and store them in a safe place for later use.

Download the sample code package

Download and unzip the sample code package (download
it
from this tutorial). Navigate to the root of the unzipped
code_package directory in the terminal and run npm install.
This command will download all the modules required to run the sample
client. When complete, you should see a node_modules folder in your
code_package root.

Deploy the sample chaincode

Before you can run the client, you need to deploy the chaincode_sample.go
found under code_package/chaincode/ to the IBM Blockchain service you
created earlier.

  1. Navigate to your IBM Blockchain service dashboard, and click
    APIs:

  2. Under the APIs section, click Network’s Enroll IDs.
    This section lists users that are already registered with the
    blockchain network and need only to be enrolled before they can deploy
    and invoke the chaincode.

    Select the user_type1_0 user. Make a note of the ID and Secret for
    this user.

    Note: Do not enroll the admin and WebAppAdmin
    users.

    These are special users that have the rights to register
    other users. Our Node.js client will take care of enrolling and
    leveraging these users.

  3. Navigate to the /registrar tab and populate the
    payload with the enroll id and enroll secret:

    {
      "enrollId": "user_type1_0",
      "enrollSecret": "5c9db3deb6"
    }

    Click the Try It Out button to execute the REST
    call to the selected peer in the blockchain network. You will get
    a response saying the user has been successfully logged in. Now
    you can use the user_type1_0 for deploying the chaincode.

  4. Navigate to the /chaincode tab.

    Click the
    text area next to the DeploySpec section. Replace the
    contents of the text area with this:

    {
    	"jsonrpc": "2.0",
    	"method": "deploy",
    	"params": {
    		"type": 1,
    		"chaincodeID": {
    			"path": "http://bit.ly/2qZBjbG"
    		},
    		"ctorMsg": {
    			"function": "init",
    			"args": [
    				
    			]
    		},
    		"secureContext": "user_type1_0"
    	},
    	"id": 0
    }

    chaincodeID is the path to the chaincode_sample.go
    file in a GitHub repository. For this tutorial, I have already
    created the repository and uploaded the client code along with the
    chaincode for your use.

  5. Click the Try It Out button to execute the Deploy
    request. You should get a response similar to this:

    {
    	"jsonrpc": "2.0",
    	"result": {
    		"status": "OK",
    		"message": "36ebb862f73a0e18701338fe2b8de99e31202ff8649c8b9909fa0c0fada22b127a997e5f8bf074f35a36a60e765a2919a8a405ee29f"
    	},
    	"id": 1
    }

    Copy the contents of the message key and
    save it. This represents the chaincode ID for the chaincode
    that was just deployed.

  6. Open the code_package/config/runtime.json file in an editor:
    {
        "env": "local",
        "processname": "BlockchainSampleClient",
        "request": {
            "size": "16mb"
        },
    
        "chaincode": {
            "id": "",
            "name": "cp_chaincode"
        },
    
        "databases": {
            "devWorksAppMaster": "devworks_app_master"
    
        },
    
        "VCAP_SERVICES": {
            "cloudantNoSQLDB": [{
                "name": "Cloudant NoSQL DB-vern",
                "label": "cloudantNoSQLDB",
                "plan": "Lite",
                "credentials": {
                    
                }
            }],
            "ibm-blockchain-5-prod": [{
                "name": "Blockchain-v6-devWorks",
                "label": "ibm-blockchain-5-prod",
                "plan": "ibm-blockchain-plan-5-prod",
                "credentials": {
                    
                }
            }]
        },
    
        "log4js": {
            "replaceConsole": true,
            "appenders": [{
                "type": "console"
            }, {

    Under the chaincode object, replace the value for
    id with the chaincode ID you saved in step
    4-5.

    Replace the contents of the credentials object under
    the ibm-blockchain-5-prod object with the service
    credentials
    you obtained from the Blockchain service on
    Bluemix in step 1-3. The service credentials have a cert link to
    download the certificate used for TLS communication between the
    client and the Blockchain service:
    “cert”:
    http://bit.ly/2quavwY

    Though I have already included the cert in the code_package, I
    recommend that you replace that with the cert found at the link
    above. Place the cert in the root folder of the code_package.

    Similarly, if you want to use the Cloudant-based key value store
    implementation for running the client, replace the contents of the
    credentials object under the
    cloudantNoSQLDB object with the
    service credentials you obtained from the Cloudant
    service on Bluemix in step 2-3.

Code package structure

Let’s take a quick detour to examine the code_package structure, and then
we’ll resume with step 7.

  • The blockchain_sample_client.js contains the client
    code that will register and enroll a user, and create and fetch the
    loan application from the blockchain.
  • The chaincode contains the
    chaincode_sample.go file to deploy to the
    blockchain and subsequently invoke.
  • The config folder contains the
    runtime.json file, which you updated earlier. It
    contains all configuration information for the client app.
  • The src folder contains all the source code that the
    blockchain_sample_client depends on.
  • The src/blockchain/blockchain_network.js file
    contains all the setup and bootstrapping code for setting up and
    configuring the client to talk to the blockchain network.
  • The src/blockchain/blockchain_sdk.js file contains
    all the Hyperledger Fabric Client code that is used to interact with
    the blockchain network for registration, enrollment, invocation, and
    query.
  • The src/database/datastore.js contains the code
    required to talk to the Cloudant service on Bluemix.
  • The src/database/model/kv_store_model.js is the
    Cloudant-based key value store implementation that the Hyperledger
    Fabric Client client will use for managing the ECerts and user
    metadata.
  1. Navigate to code_package/ in the terminal and run:
    node blockchain_sample_client.js

    Here’s a snippet of the output:

    The sample client was able to successfully create a mortgage
    application on blockchain and subsequently fetch it from the
    blockchain ledger.

Blockchain sample client

Let’s now look at the client code in detail to understand how it works:

function runClient(){
    
    logHelper.initialize(config.processname, config.env, config.log4js);
    logger = logHelper.getLogger('blockchain_sample_client.js');

    var user = 'vojha25';
    var affiliation = 'Bank_Home_Loan_Admin';

    var args = process.argv.slice(2);
    if(args.length >=1){
        var input = JSON.parse(args);
        if(validate.isValidString(input['user'])){
            user = input['user'];
        }
        if(validate.isValidString(input['affiliation'])){
            affiliation = input['affiliation'];
        }
    }

    setup()
    .then(function(resp){
         return bcSdk.recursiveRegister({username: user, affiliation: affiliation}) 
    })
    .then(function(resp){
        return bcSdk.recursiveLogin({username: user, password: resp['body']['password'] })
    })
    .then(function(resp){
        var id = Math.floor(Math.random() * (100000 - 1)) + 1;
        var maStr = '{"propertyId":"prop1","landId":"land1","permitId":"permit1","buyerId":"vojha24","personalInfo":{"firstname":"Varun","lastname":"Ojha","dob":"dob","email":"varun@gmail.com","mobile":"99999999"},"financialInfo":{"monthlySalary":162000,"otherExpenditure":0,"monthlyRent":41500,"monthlyLoanPayment":40000},"status":"Submitted","requestedAmount":4000000,"fairMarketValue":5800000,"approvedAmount":4000000,"reviewedBy":"bond","lastModifiedDate":"21/09/2016 2:30pm"}';
        var ma = JSON.parse(maStr);
        ma['id'] = 'la'+id;
        return bcSdk.createMortgageApplication({user: user, mortgageApplication: ma})

    })
    .then(function(resp){
        var ma = resp.body;
        return bcSdk.getMortgageApplication({user: user, id: ma['id']})
    })
    .then(function(resp){
        logHelper.logMessage(logger,"runClient","Fetched mortgage application",resp.body);
    })
    .catch(function(err){
        logHelper.logError(logger,"runClient","Error Occurred",err);
    })
}

The blockchain_sample_client.js shown in the listing above
does the following:

  1. Reads command line parameters passed in while invoking the client. The
    client expects two values: username and affiliation. The client will
    use default values if none are provided during client invocation.
  2. The client then performs the setup of the blockchain network. During
    setup, the chain object is initialized and configured with all the
    peers and membership services that the client intends to talk to. The
    cloudant datastore Is also initialized as part of the setup.
  3. The client then goes on to register the default user or the one passed
    in as a command line parameter.
  4. This is followed by the enrollment step, where the one-time password
    retrieved during registration is used for obtaining the ECert pair.
    This ECert pair will be stored in the Cloudant-based key value
    store.
  5. The client then creates a sample mortgage application json object.
    This client then invokes the createMortgageApplication API in the
    blockchain_sdk.js file. This createMortgageApplication method invokes
    the appropriate chaincode method in blockchain and passes in the loan
    application JSON content.
  6. Once the mortgage application has been successfully created, the
    client invokes the getMortgageApplication method, which in turn
    queries the blockchain by invoking the appropriate chaincode method to
    fetch the mortgage application created in the previous step.

Network setup functions

Setup

Let’s now look at the setup method in detail:

function setup(){
    return new Promise(function(resolve, reject){
        try{
            logHelper.logMethodEntry(logger,"setup");

            //Fetch IBM Bluemix Cloudant and Blockchain service instance configuration
            var cloudantConfig = config.VCAP_SERVICES[constants.VCAP_SERVICES_CLOUDANT][0];
            var blockchainConfig = config.VCAP_SERVICES[constants.VCAP_SERVICES_BLOCKCHAIN][0];
            
            //Setup datastore
            var result = datastore.initSync(cloudantConfig);
            if(result.statusCode != constants.SUCCESS){
                logHelper.logError(logger,'Could not initialize datastore', result);
                return reject({statusCode: 500, body: ''});
            }

            //Setup Cloudant based KeyValueStore
            var cloudantSetupDone = false;
            getCloudantKeyValStore(datastore, config.databases[constants.APP_MASTER_DB])
            .then(function(resp){
                cloudantKvStore = resp.body;
                cloudantSetupDone = true;
                return blockchainNetwork.setupBlockchain({blockchainConfig: blockchainConfig, ccName: constants['BLOCKCHAIN_CHAINCODE_NAME'] , kvStore: cloudantKvStore })
            })
            .then(function(resp){
                return resolve({statusCode: 200, body: ''});
            })
            .catch(function(err){
                if(cloudantSetupDone != true){
                    logHelper.logError(logger,'Could not initialize CloudantKeyValueStore', err);
                }
                else{
                    logHelper.logError(logger,'Blockchain setup failed. exiting...',err);
                }
                return reject(err);
            });
            
            
        }
        catch(err){
            logHelper.logError(logger,'Could not complete setup', err);
            throw reject({statusCode: 500, body: err});
        }
    })
    
}

In the listing above:

  • Lines 7 and 8 fetch the Cloudant and Blockchain
    service credentials from the runtime.json file, which we had updated
    earlier. The config module parses the runtime.json file and makes the
    contents available as a JSON object.
  • Lines 11 to 15 initialize the Cloudant datastore. The
    datastore.js file will be used to obtain a handle to a DB instance,
    which can be used to perform CRUD operations on our Cloudant
    database.
  • Lines 18 to 24 initialize the custom Cloudant-based
    key value store implementation with the DB instance obtained earlier.
    This key value store will be used by the Hyperledger Fabric Client SDK
    chain object to manage ECerts and other user metadata. This is
    followed by the blockchain network setup.

Blockchain network setup

Let’s now look at the setupBlockchain method.

function setupBlockchain(params){
    return new Promise(function(resolve,reject){
        try{
            logHelper.logEntryAndInput(logger, 'setupBlockchain', params);

            if(!validate.isValidJson(params)){
                logHelper.logError(logger, 'setupBlockchain', 'Invalid params');
                return reject({statusCode: constants.INVALID_INPUT, body: 'Could not setup blockchain. Invalid params' });
            }
            var goPath = __dirname+'/../../chaincode/';
            process.env.GOPATH = goPath;
            
            
            chainInit({ccName: params.ccName, kvStorePath: params.kvStorePath, kvStore: params.kvStore})
            .then(function(resp){
                return loadNetworkConfiguration(params.blockchainConfig);
            })
            .then(function(resp){
                return configureNetwork();
            })
            .then(function(resp){
                logHelper.logMessage(logger, 'setupBlockchain', 'blockchain setup complete');
                isSetupComplete = true;
                return resolve({statusCode: constants.SUCCESS, body: 'blockchain setup complete'});
            })
            .catch(function(err){
                logHelper.logError(logger, 'setupBlockchain', 'Could not setup blockchain', err);
                return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not setup blockchain', err});
            });
        }
        catch(err){
            logHelper.logError(logger, 'setupBlockchain', 'Could not setup blockchain', err);
            return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not setup blockchain', err});
        }
    });
}

The setup consists of three functions. Line 14 starts off
with chainInit followed by loadNetworkConfiguration
and finally with configureNetwork.

Here is the snippet to initialize the chain:

var kvStore = params.kvStore;
            if(!validate.isValid(kvStore)){
                logHelper.logError(logger, 'chainInit', 'Invalid kvStore');
                return reject({statusCode: constants.INVALID_INPUT, body: 'Could not initialize Chain. Invalid kvStore' })
                
            }

            chain = hfc.newChain(ccName);
            // Configure the KeyValStore which is used to store sensitive keys
            // as so it is important to secure this storage.
            chain.setKeyValStore(kvStore);
            chain.setECDSAModeForGRPC(true);
  • Lines 1 to 6 take care of input validation.
  • On line 8 a new chain object is created using the
    Hyperledger Fabric Client SDK module hfc.
  • On line 11 the chain object is configured with the
    key value store implementation to use for managing ECerts for the
    users. In this case, it is the Cloudant key value store
    implementation. The Hyperledger Fabric Client SDK also provides a
    default file-based key value store implementation that can be used.
    (But for deploying the blockchain client application on the cloud, the
    file based implementation is not advised, because redeployments of the
    client code will lead to loss of the file-based key value store. In
    this case, all previously registered users would be rendered useless
    since they would not have the required certs to authenticate.)
  • On line 12 the cipher suite is set to ECDSA for TLS
    communication.

Configuration properties for the chain object

  • IsSecurityEnabled: If the chain object is configured
    with a valid membership services endpoint, security is considered to
    be enabled. Security enabled means that a TCert will be used to
    generate an ECDSA-based digital signature that will be sent as a part
    of all chaincode invocation transactions. This TCert will be used by
    the respective peer to authenticate the caller.
  • IsPreFetchMode: In pre-fetch mode, the chain object
    will request a batch of TCerts from the membership services upon
    initialization instead of waiting for a transaction invocation call.
    The intent is to boost performance.
  • TCertBatchSize: The number of TCerts to be returned
    as a part of the get TCert batch request. Default value is 200.

Load blockchain network
configuration

Let’s look at the loadNetworkConfiguration method:

function loadNetworkConfiguration(blockchainConfig){
    return new Promise(function(resolve, reject){
        try {
            logHelper.logEntryAndInput(logger, 'loadNetworkConfiguration');
            
            if(!validate.isValidJson(blockchainConfig)){
                logHelper.logError(logger, 'loadNetworkConfiguration', 'Invalid blockchainConfig');
                return reject({statusCode: constants.INVALID_INPUT, body: 'Could not loadNetworkConfiguration. Invalid blockchainConfig' });
            }
            
            var bcService = blockchainConfig;
            
            var peers = bcService.credentials.peers;
            for (var i in peers) {
                peerURLs.push(constants['BLOCKCHAIN_NW_PROTOCOL'] + peers[i].discovery_host + ":" + peers[i].discovery_port);
                peerHosts.push("" + peers[i].discovery_host);
            }
            var ca = bcService.credentials.ca;
            for (var i in ca) {
                caURL = constants['BLOCKCHAIN_NW_PROTOCOL'] + ca[i].url;
            }

            //users are only found if security is on
            if (bcService.credentials.users) users = bcService.credentials.users;
            for (var z in users) {
                if (users[z].username == constants['BLOCKCHAIN_REGISTRAR_ID']) {
                    registrarPassword = users[z].secret;
                }
            }

            logHelper.logMessage(logger, 'loadNetworkConfiguration', 'Successfully loaded network configuration');
            return resolve({statusCode: constants.SUCCESS, body: 'Successfully loaded network configuration'});

        }
        catch (err) {
            logHelper.logError(logger, 'loadNetworkConfiguration', 'Could not load Network Configuration', err);
            return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not load Network Configuration'});
        }
    });
    
}
  • Lines 13 to 17 parse the blockchain service
    configuration to retrieve the list of peers and add the discovery url
    for these peers to the peerHosts collection.
  • Lines 18 to 21 retrieve the url for the CA
    (membership services). The value for
    constants[‘BLOCKCHAIN_NW_PROTOCOL’] is “grpcs”. IBM Bluemix Blockchain
    service only supports secure communication.
  • Lines 23 to 29 retrieve the password from the list of
    pre-registered users available in the blockchain service credentials
    for WebAppAdmin. As stated earlier, the WebAppAdmin user has registrar
    rights. This means the WebAppAdmin user can be used to register other
    new users onto the blockchain. This password will be used for
    enrolling the WebAppAdmin later.

Blockchain network
configuration

Let’s look at the configureNetwork method:

function configureNetwork() {
    return new Promise(function(resolve, reject){

        try{
            logHelper.logEntryAndInput(logger, 'configureNetwork');
            var pem;
            fs.readFile(constants['BLOCKCHAIN_NW_CERT_PATH'], function(err, data){
                if(validate.isValid(err)){
                    logHelper.logError(logger,'configureNetwork', 'Could not read cert: '+constants['BLOCKCHAIN_NW_CERT_PATH']);
                    return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not read cert: '+constants['BLOCKCHAIN_NW_CERT_PATH'] });
                }
                else{
                    pem = data;
                    chain.setMemberServicesUrl(caURL, { pem: pem });
                    for (var i in peerURLs) {
                        chain.addPeer(peerURLs[i], { pem: pem });
                    }

                    recursiveLogin({username: constants['BLOCKCHAIN_REGISTRAR_ID'], password: registrarPassword, chain: chain })
                    .then(function(resp){
                        logHelper.logMessage(logger,'configureNetwork', 'Successfully enrolled registrar: '+constants['BLOCKCHAIN_REGISTRAR_ID']);
                        var registrarMember = resp.body;
                        chain.setRegistrar(registrarMember);
                        return resolve({statusCode: constants.SUCCESS, body: 'Network configuration complete'});
                    })
                    .catch(function(err){
                        return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not enroll registrar: '+constants['BLOCKCHAIN_REGISTRAR_ID'] });
                    })

                }

            });
       
        }
        catch(err){
            logHelper.logError(logger, 'configureNetwork', 'Could not configure network', err);
            return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not configure network', err});
        }
    });
    
}
  • Line 7 reads the pem file we downloaded from the cert
    path provided in the Blockchain service credentials. TLS is enabled
    for all communication in the Bluemix Blockchain network. This also
    includes the communication between the client application and the
    network entities, such as membership services and the peers.
  • Lines 14 to 17 add the membership services and peers
    URLs obtained earlier to the chain object along with the pem file. The
    pem file be used by the chain object while communicating with the
    membership services and peers.
  • Lines 19 to 28 enroll the WebAppAdmin in the
    blockchain network. As explained earlier, enrollment will generate the
    ECert pair, which will be stored by the chain into the cloudant key
    value store. If you look at the documents in your cloudant database
    via the cloudant service dashboard you should see an entry for
    member.WebAppAdmin now, as in this example:

    {"name":"WebAppAdmin","enrollment":{"key":"f4b19c7195d2da0ea
    02a47fa8e2aabdc0b4ba610720a696e895b400fb81cb9be","cert":"d2
    d2d2d454e44204543445341205055424c4943204b45592d2d2d2d2d
    0a","queryStateKey":{"type":"Buffer","data":[91,181,140,162,159,21
    8,158,144,230,192,52,99,100,155,235,23,72,242,97,158,82,29,54,22
    2]}}}
  • Line 23 sets the registrar for the chain to the
    WebAppAdmin member object. This registrar will be used by the chain
    for registering new members/users to the blockchain.

This completes the blockchain setup. Now look at additional key
methods:

Usage functions

Register a new user

Let’s look at the doRegister method:

var roles = params.roles;
            if(!validate.isValidArray(roles)){
                roles = ['client'];
            }

            var enrollsecret
            var chain = bcNetwork.getChain();
            var reg = chain.getRegistrar();
            var chainAsync = Promise.promisifyAll(chain);

            chainAsync.getMemberAsync(username)
            .then(function(member){
                var memberAsync = Promise.promisifyAll(member);
                
                    var registrationRequest = {
                        enrollmentID: username,
                        attributes: [
                            {name: 'role', value: affiliation},
                            {name: 'username', value: username}
                        ],
                        affiliation: 'group1',
                        registrar: reg,
                        roles: roles
                        
                    };
                    
                return memberAsync.registerAsync(registrationRequest);
            })
            .then(function(enrollsec){
                logHelper.logMessage(logger, 'registerUser', 'Successfully registered user on blockchain: '+username);
                enrollsecret = enrollsec;
                return resolve({statusCode: constants.SUCCESS, body: {password: enrollsecret}});
                
            })

In order to register a new user to the blockchain based on Hyperledger
Fabric, the following fields can be sent as a part of the registration
request:

  1. enrollmentID (string): The enrollment id for this
    member/user.
  2. roles (string[]): Represents the roles associated
    with this member. Valid roles are client (default),
    peer, validator, and
    auditor.
  3. affiliation (string): Affiliation for this member.
    Affiliations can be found in the manifest.yaml file. For the
    Blockchain service on Bluemix, the parent affiliation is
    group1.
  4. attributes (Attribute[]): The attribute names and
    values to grant to this member. These attributes are user-defined and
    are stored in the ACA. All or some of these attributes will be
    embedded in the TCert requested by this member. The same attributes
    will be available to the chaincode for implementing access control by
    parsing the caller TCert.
  5. registrar: A user can have the role of a registrar,
    which would enable the user to register other members. A member object
    with registrar rights needs to be included as a part of the request.
    This registrar member will be used to register the requested member.
  • Lines 7 and 8 retrieve the chain object from the
    blockchain_network.js file and retrieve the registrar object from the
    chain object.
  • Line 11 retrieves the member object from the chain.
    For any interaction with the blockchain network based on Hyperledger
    Fabric, we must have a handle to the member object on whose behalf all
    chaincode deployments and invocations can occur. The getMember method
    retrieves the member ECert and other metadata information from the
    Cloudant key value store.
  • Lines 15 to 23 create a registration request. Please
    note the attribute key/value pairs for username and
    role. Part 1 of this tutorial series showed how the
    chaincode leveraged these attributes for implementing access
    control.
  • Line 27 invokes the register method on the member
    object, and subsequent code retrieves the one-time password token
    returned from the ECA to be used in the enrollment process.

Enroll a user

Here is the code snippet for the doLogin method:

var chain = bcNetwork.getChain();
            var chainAsync = Promise.promisifyAll(chain);

            chainAsync.getMemberAsync(username)
            .then(function(member){
                var memberAsync = Promise.promisifyAll(member);
                return memberAsync.enrollAsync(password);
            })
            .then(function(crypto){
                logHelper.logMessage(logger, 'doLogin', 'Successfully logged in user on blockchain: '+username);
                return resolve({statusCode: constants.SUCCESS, body: crypto});
            })
            .catch(function(err){
                logHelper.logError(logger, 'doLogin', 'Could not login user on blockchain: '+username, err);
                return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not login user' });
            });
  • As before, lines 1 to 6 retrieve the chain object and
    fetch the member object from the chain.
  • Line 7 invokes the enroll method on the member object
    passing in the password retrieved from the register response.
  • Line 9 successfully retrieves the ECert pair from the
    ECA.

Create the Mortgage
application

Here is the code snippet for the createMortgageApplication
method:

function createMortgageApplication(params) {
    return new Promise(function(resolve, reject){
        var mortgageApplication;
        try{
            logHelper.logEntryAndInput(logger, 'createMortgageApplication', params);

            if(!validate.isValidJson(params)){
                logHelper.logError(logger, 'createMortgageApplication', 'Invalid params');
                return reject({statusCode: constants.INVALID_INPUT, body: 'Could not create mortgage application. Invalid params' })
            }

            var user = params.user;
            if(!validate.isValidString(user)){
                logHelper.logError(logger, 'createMortgageApplication', 'Invalid user');
                return reject({statusCode: constants.INVALID_INPUT, body: 'Could not create mortgage application. Invalid user' })
            }

            mortgageApplication = params.mortgageApplication;
            if(!validate.isValidJson(mortgageApplication)){
                logHelper.logError(logger, 'createMortgageApplication', 'Invalid mortgageApplication');
                return reject({statusCode: constants.INVALID_INPUT, body: 'Could not create mortgage application. Invalid mortgageApplication' })
            }

           
            var id = mortgageApplication['id'];
            var payload = JSON.stringify(mortgageApplication);

            var reqSpec = getRequestSpec({functionName: 'CreateLoanApplication', args: [id, payload]});
            recursiveInvoke({requestSpec: reqSpec, user: user})
            .then(function(resp){
                logHelper.logMessage(logger, 'createMortgageApplication', 'Successfully created mortgageApplication', resp.body);
                return resolve({statusCode: constants.SUCCESS, body: mortgageApplication});
            })
            .catch(function(err){   
                logHelper.logError(logger, 'createMortgageApplication', 'Could not create mortgageApplication', err);
                return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not create mortgageApplication' });

            });

        }
        catch(err){
            logHelper.logError(logger, 'createMortgageApplication', 'Could not create mortgage application on blockchain ledger: ', err);
            return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not create mortgage application' });
        }
    });
}
  • Lines 7 to 22 perform input validation. The method
    expects two parameters: User (that will be used to invoke
    the chaincode transaction) and the mortgage application JSON content
    to persist on the blockchain.
  • Line 28 creates the request spec that needs to be
    used for invoking the chaincode.
  • Line 29 calls the recursiveInvoke
    function that actually invokes the chaincode per the request spec
    provided.

Request spec for chaincode
invocation

Here is the code snippet for the getRequestSpec method:

function getRequestSpec(params){

        if(!validate.isValidJson(params)){
            logHelper.logError(logger, 'getRequestSpec', 'Invalid params');
            throw new Error("Invalid params");
        }

        var chaincodeID = config['chaincode']['id']
        if(!validate.isValidString(chaincodeID)){
            logHelper.logError(logger, 'getRequestSpec', 'Invalid chaincodeID');
            throw new Error("Invalid chaincodeID");
        }

        var functionName = params.functionName;
        if(!validate.isValidString(functionName)){
            logHelper.logError(logger, 'getRequestSpec', 'Invalid function name');
            throw new Error("Invalid function name");
        }

        var args = []
        
        if(validate.isValidArray(params.args)){
            args = params.args;
        }

        var attributes = ['username', 'role']
        
        if(validate.isValidArray(params.attributes)){
            attributes = params.attributes;
        }

        var spec = {
            chaincodeID: chaincodeID,
            fcn: functionName,
            args: args,
            attrs: attributes
        }

        return spec;
}
  • Line 8 retrieves the chaincode ID for the deployed
    chaincode from the config (runtime.json). This is the chaincode we
    want to invoke.
  • Line 14 retrieves the name of the function to invoke
    in the chaincode. In this case, it is the
    CreateLoanApplication method.
  • Line 26 puts in the attribute keys. These have been
    hard-coded to role and username but should ideally be passed in as
    parameters to the getRequestSpec method.

    Note:
    If the attribute keys are not passed in as part of the request
    spec, these attributes will not be sent as a part of the get
    TCert request to the TCA and hence, the resultant TCert will
    not have any embedded attributes.
    When the chaincode
    tries to parse this TCert to retrieve the specific attributes (for
    example, role and username) for access control, it will fail.
    Lines 32 to 37 contain the actual request
    spec schema.

Invoke the chaincode deployed on the
blockchain

Here is the code snippet for the doInvoke method:

var chain = bcNetwork.getChain();
            var chainAsync = Promise.promisifyAll(chain);
            

            chainAsync.getMemberAsync(user)
            .then(function(member){
                
                var tx = member.invoke(requestSpec);
                tx.on('submitted', function(data) {
                    logHelper.logMessage(logger, 'doInvoke', 'Transaction for invoke submitted ',requestSpec);
                    return resolve({statusCode: constants.SUCCESS, body: data});
                    
                });

                tx.on('complete', function(data) {
                    //logHelper.logMessage(logger, 'doInvoke', 'Transaction for invoke complete ',data);
                    
                });

                tx.on('error', function (err) {
                    logHelper.logError(logger, 'doInvoke', 'Could not perform invoke ',err);
                    return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not perform invoke ' });
                   
                });
            })
  • Lines 1 to 7 repeat the process of obtaining the
    chain object and fetching the member object.
  • Line 8 calls the invoke method on the
    member object to invoke the chaincode per the request spec passed in.
    This invocation returns a Transaction object.

Note 1: You need to register for submitted,
complete, and error events on the
transaction object to get notified of the status of the
transaction
:

  • Submitted: The transaction has been successfully
    submitted to the blockchain network and will be executed by the
    network. A Transaction UUID is returned from the blockchain
    network.
  • Error: The transaction could not be submitted to the
    blockchain network or could not be accepted by the blockchain
    network.
  • Complete: The transaction was completed. This does
    not signify successful completion.
    (The complete event gets called along
    with submitted and error events.)

Note 2: The invoke request returns only the transaction UUID for
the chaincode invocation. It does not return any data being returned
from the chaincode function itself.

Note 3: Even if the actual chaincode function threw an error and
did not complete successfully, the error event will not get called.
The complete event will get called.

The approach I follow is to wait for the submitted event. This is an
indication that the chaincode function will be invoked, at least. To
ensure successful execution of chaincode, you can either:

  • Subscribe to an event that will be generated after successful
    execution of the chaincode function.
  • Query the blockchain.

Fetch the Mortgage application from the
blockchain

Here is the code snippet for the getMortgageApplication
method:

function getMortgageApplication(params) {
    return new Promise(function(resolve, reject){
       
        try{
            logHelper.logEntryAndInput(logger, 'getMortgageApplication', params);

            if(!validate.isValidJson(params)){
                logHelper.logError(logger, 'getMortgageApplication', 'Invalid params');
                return reject({statusCode: constants.INVALID_INPUT, body: 'Could not fetch mortgage application. Invalid params' })
            }

            var user = params.user;
            if(!validate.isValidString(user)){
                logHelper.logError(logger, 'getMortgageApplication', 'Invalid user');
                return reject({statusCode: constants.INVALID_INPUT, body: 'Could not fetch mortgage application. Invalid user' })
            }

            var id = params.id;
            if(!validate.isValidString(id)){
                logHelper.logError(logger, 'getMortgageApplication', 'Invalid id');
                return reject({statusCode: constants.INVALID_INPUT, body: 'Could not fetch mortgage application. Invalid id' })
            }

            var reqSpec = getRequestSpec({functionName: 'GetLoanApplication', args: [id]});
            recursiveQuery({requestSpec: reqSpec, user: user})
            .then(function(resp){
                logHelper.logMessage(logger, 'GetMortgageApplication', 'Successfully fetched mortgage application', resp.body);
                return resolve({statusCode: constants.SUCCESS, body: resp.body});
            })
            .catch(function(err){   
                logHelper.logError(logger, 'GetMortgageApplication', 'Could not fetch mortgage application', err);
                return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not fetch mortgage applications' });

            });

        }
        catch(err){
            logHelper.logError(logger, 'getMortgageApplication', 'Could not fetch property ad ', err);
            return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not fetch mortgage application ' });
        }
    });
}

The getMortgageApplication method is similar in structure to
the createMortgageApplication method. It accepts the user on
whose behalf the chaincode has to be invoked along with the ID for the
mortgage application to fetch.

This is followed by the getRequestSpec method for creating the
request spec as before and invoking the recursiveQuery method
to query the blockchain.

Use chaincode to query the
blockchain

Here is the code snippet for the doQuery method:

var chain = bcNetwork.getChain();
            var chainAsync = Promise.promisifyAll(chain);

            chainAsync.getMemberAsync(user)
            .then(function(member){
                
                var tx = member.query(requestSpec);
                tx.on('submitted', function() {
                    logHelper.logMessage(logger, 'doQuery','Transaction for query submitted');
                });

                tx.on('complete', function(data) {
                    try{
                        logHelper.logMessage(logger, 'doQuery', 'Transaction for query complete ',requestSpec);
                        var buffer = new Buffer(data.result);
                        var jsonResp = JSON.parse(buffer.toString());
                        return resolve({statusCode: constants.SUCCESS, body: jsonResp});
                    }
                    catch(err){
                        logHelper.logError(logger,'doQuery','Could not parse query response',err);
                        return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not parse query response ' });
                    }
                });

                tx.on('error', function (err) {
                    logHelper.logError(logger, 'doQuery', 'Could not perform query ',err);
                    return reject({statusCode: constants.INTERNAL_SERVER_ERROR, body: 'Could not perform query ' });
                   
                });
            })
  • Line 7 invokes the query method on the
    member object passing in the appropriate request spec.

When the query transaction has successfully executed on the blockchain, the
complete event will be called. The data read from the
blockchain needs to be read into a buffer. Because our chaincode
implementation stored the data as a JSON string, the returned content
needs to be parsed as JSON.

Subscribe to events

A blockchain network based on Hyperledger Fabric has a built-in event hub
that our client application will connect to and listen for events.

Here is the code snippet for connecting to the event hub and subscribing to
events. This snippet is in the blockchain_network.js file, but it has been
commented out due the second item noted in the Known
issues
section below.

chain.eventHubConnect(peerEventHosts[0],{pem:pem});
setupEvents();

/**
 * Sample method to showcase how to subscribe and consume events emitted from blockchain
 */
function setupEvents(){
    try{
    var eh = chain.getEventHub();
    var cid = config['chaincode']['id'];
    var regid = eh.registerChaincodeEvent(cid, "^eventSender$", function(event) {
        console.log(event);
        var buffer = new Buffer(event.payload);
        console.log(buffer.toString());
    });
    console.log("EVENT SETUP DONE");
}
catch(err){
    console.log(err);
    console.log("Could not setup events");
}
}

process.on('exit', function (){
    console.log('exit called');
    chain.eventHubDisconnect();
});
  • Line 1 connects to the event hub for the specified
    peer. The service credentials for the Blockchain service in
    runtime.json has the event_host and
    event_port values for each peer. These are used to
    establish the connection to the event hub.
  • Line 2 invokes the setupEvents() method
    that can be used to subscribe to and handle events from the
    blockchain.
  • Line 11 shows how to register for a chaincode event
    and defines the handler function when the event is published. The
    chaincode that we deployed emits an event on successful creation of
    the loan application. Here we are registering for the same event.

If you run the client with events enabled (uncomment the event code in
blockchain_network.js), the createLoanApplication event
defined in the chaincode will be received by the client and printed to the
console.

Known issues with Hyperledger Fabric
Client SDK

  • Random/intermittent security handshake failure errors while
    calling invoke, query, deploy on member object.

    This error occurs when TLS is enabled for communication. One of the
    primary reasons for this failure is not having enough file
    descriptors available in the operating system. It occurs most
    frequently on MAC machines (EL Capitan), less frequently on
    Windows 7 machines, and almost negligibly when the
    application/client is deployed and run on Bluemix.

    To work around this issue during development and testing, I wrap
    all Hyperledger Fabric Client SDK calls to the blockchain network
    in recursive functions. The retry interval and number of retries
    can be changed by modifying the retryInterval and
    retryLimit variables in the blockchain_sdk.js
    file.

  • EventHub not connecting or crashing post connection.

    This error has been attributed to open bugs in some dependent
    third-party libraries, such as the grpc node module.

Conclusion

You’ve now seen the steps and code for developing your own Node.js client
to talk to a blockchain network based on Hyperledger Fabric v0.6, and for
deploying the chaincode you developed in Part 1 onto the IBM Blockchain
service on Bluemix so you can invoke it from your client application.
Download the code associated with this tutorial and get started.

This concludes this tutorial series based on Hyperledger Fabric v0.6. I’m
updating this series for Hyperledger Fabric v1.0, so stay tuned!


Downloadable resources



#blockchain,#awvi,#ibm,#Hyperledger

Innovation

via IBM developerWorks : Cloud computing https://ibm.co/2cihRPX

May 23, 2017 at 01:06PM