Hyperledger Composer basics, Part 2

Refine and deploy your blockchain network

Install development tools, unit test, and deploy your network to the IBM
Cloud

Content series:

This content is part # of # in the series: Hyperledger Composer basics, Part 2

Stay tuned for additional content in this series.

This content is part of the series:Hyperledger Composer basics, Part 2

Stay tuned for additional content in this series.

This tutorial builds on Part 1, where you learned how to model and test a
simple business network in a local version of the Hyperledger Composer
Playground. Now let’s go deeper into Hyperledger Composer and ultimately
import your network model to the Online Playground on the IBM Cloud.

You’ll first need to install a few developer tools; the video in this tutorial will guide you through
the installation. Then you’ll make changes to the sample Perishable Goods network that you worked with in Part 1.
Specifically, you’ll model an IoT GPS sensor in the shipping container by
adding GPS readings to the Shipment asset, and modify the smart contract
(chaincode) to send an alert when the Shipment reaches its destination
port.

You’ll also learn how to unit test your blockchain networks by using a
Behavior-Driven Development (BDD) tool called Cucumber. You’ll use a Cucumber feature
file to test the smart contract’s logic.

Finally, you’ll import the model into the Online Playground hosted on the
IBM Cloud, where you can interact with the model and submit transactions
through the Playground UI, just like you did in Part 1. Because the Online
Playground is on the IBM Cloud, no installation is needed to use it.

Prerequisites

Beyond these few prerequisites, the next section will walk you through the
developer tools you need to install.

At the time of this writing, Composer is supported only on Ubuntu Linux and
MacOS, but Windows support is in the works.

For most of the tools you need to install, the installation instructions
for Ubuntu Linux and MacOS are virtually the same, and I’ll call out the
few differences as we go. (To watch how to set up your computer, see the
video.)

The easiest way to install Node.js is to use nvm, which stands for Node
Version Manager. As the name implies, nvm is used to manage the version of
Node that is installed on your computer. To install nvm, follow the
instructions that match your platform.

Install nvm on Ubuntu Linux

The Hyperledger Composer team provides a script to install nvm. To install
nvm, execute the following:

                curl -O http://bit.ly/2kfeIXF
                chmod u+x prereqs-ubuntu.sh
                ./prereqs-ubuntu.sh

You’ll be prompted for your password (make sure you are a sudoer on your
system). For more information, visit Installing and developing with Hyperledger Composer.

When the script completes, you’re ready to install Node.js. Skip to the “Use nvm to install Node.js” section now.

Install nvm on MacOS

MacOS provides its own version of many popular command-line tools like git,
make, and svn. To see if you have the xcode command-line tools installed,
open a terminal window, type git, and press Enter.

Figure 1. If you see this message, you need to install the
command-line tools

If you see this message, you need to install the                     command-line tools

If you see a message that looks like Figure 1, click the
Install button to install the command-line tools. The
installation takes less than 5 minutes.

Run this command from the terminal:

                curl -o- http://bit.ly/2BEkQNt | bash

Note: Check the README on the nvm GitHub
repo
to make sure you are installing the latest version (0.33.6 at
the time of this writing).

The script makes changes to your Bash shell settings, so you need to exit
the terminal window, and open a new one to load the new settings.

To verify that nvm is installed correctly, execute the
nvm --version command from the terminal window. You’ll see
output like this:

                $ nvm --version
                0.33.6

Now you’re ready to install Node.js using nvm.

Use nvm to install Node.js

To install Node.js, go to the command line (Ubuntu) or open a terminal
window (MacOS) to run the nvm
install --lts

command to install the LTS version of Node.js:

nvm install --lts

You’ll see output like this:

                $ nvm install --lts
                Installing latest LTS version.
                Downloading and installing node v8.9.0...
                Downloading http://bit.ly/2BF5h82...
                ######################################################################## 100.0%
                Computing checksum with shasum -a 256
                Checksums matched!
                .
                .
                (LOTS MORE OUTPUT FROM THE COMPILER)
                .
                .
                Now using node v8.9.0 (npm v5.5.1)

The installation can take several minutes while the code is compiled for
your platform (this is the case for MacOS, at least), so be patient.

Finally, verify that Node.js is installed:

                $ node -v
                v8.9.0

In this case, I have installed Node.js 8.9.0, which is the latest LTS
version at the time of this writing.

Install Composer command-line interface
(CLI)

You use the command-line interface (CLI) to create, deploy, and update
business networks, and perform other functions related to your blockchain networks.

To install the Composer CLI, go to the command line (Ubuntu) or open
terminal window (MacOS), and enter this command:

                npm install -g composer-cli

npm stands for Node Package Manager and was installed when nvm installed
Node.js. Normally when you install a Node.js package through npm, it is
available only within the directory tree where you installed it.
Specifying the -g option tells npm to install the package
globally, which makes it available to any Node.js project on your
computer.

Verify that composer-cli was installed correctly. Run the
composer -v from the command line (Ubuntu) or terminal window
(MacOS), and the version number as output:

                $ composer -v
                v0.15.0

VSCode is an
open source editor from Microsoft. The source code is freely available for
download from Microsoft’s VSCode GitHub repo.

You don’t have to install VSCode to do Hyperledger Composer development,
but the Composer team recommends it, and VSCode is actually pretty nice.
There is a Hyperledger Composer extension for VSCode, which you can easily
install and enable. It provides smooth integration with Git, along with
syntax highlighting for Composer business network model files.

Install VSCode on Ubuntu Linux

To install on Ubuntu (or other Debian-based Linux), choose one of these
options:

  • .deb— Debian package
  • .rpm—RPM Package
    Manager (originally called Red Hat Package Manager)
  • .tar.gz— Tarball

Pick the method you like best, and follow the detailed
installation instructions
on the VSCode website.

Install VSCode on MacOS

To install on MacOS, click the Download for Mac button,
and a zip file containing the VSCode app will be downloaded to your Mac.
See the VSCode website for detailed
instructions
on how to install and run VSCode.

Install the Hyperledger Composer
extension

To take full advantage of syntax highlighting when editing Hyperledger
Composer files, be sure to install the Composer extension for VSCode.
Start VSCode, and click the Extensions icon on the left side of the UI (or
press Cmd + X on your Mac) to open the Extensions
editor.

Type Hyperledger in the search field, and you will see the
Hyperledger Composer extension in the list just below the Search field, as
shown in Figure 2. Click the Install button, and when
that finishes, restart VSCode to activate the extension.

Figure 2. VSCode Extension Marketplace

VSCode Extension Marketplace

Now whenever you use VSCode to edit a Hyperledger Composer project file,
its syntax will highlight automatically. Figure 3 shows the
perishable.cto model file opened in the VSCode editor window.
Notice the syntax highlighting of the namespace and
enum keywords.

Figure 3. Perishable Goods network in VSCode

VSCode Extension Marketplace

Video: Set up your
environment

This video shows you how to set up your environment.

Now that you have tools, it’s time to put them to work. You will clone the
perishable-network GitHub repository I have provided, then
build and unit test the code using the Node.js tools you just
installed.

Choose a location on your computer where you will work with Hyperledger
Composer and the network models. For example, I use
~/HyperledgerComposer as my Composer root directory, and I
set an environment variable in the Bash shell I’m using:

$ export COMPOSER_ROOT=~/HyperledgerComposer

To keep this tutorial location-agnostic, I’ll refer to this directory as
$COMPOSER_ROOT. When you see $COMPOSER_ROOT in
the examples that follow, I am referring to the location you have chosen.
I suggest you set $COMPOSER_ROOT to that location as I did
above.

Go to a command line (Ubuntu) or open a terminal window (MacOS), navigate
to your $COMPOSER_ROOT directory, and enter this command:
git clone
http://bit.ly/2ki8TIY
as
shown below.

                $ cd $COMPOSER_ROOT
                $ pwd
                /Users/sperry/HyperledgerComposer
                $ git clone http://bit.ly/2ki8TIY
                Cloning into 'developerWorks'...
                remote: Counting objects: 364, done.
                remote: Compressing objects: 100% (45/45), done.
                remote: Total 364 (delta 33), reused 78 (delta 25), pack-reused 277
                Receiving objects: 100% (364/364), 146.69 KiB | 754.00 KiB/s, done.
                Resolving deltas: 100% (162/162), done.

Now you have the code. It’s time to build and test it.

You use the Node.js package manager (npm) to run a build, and then execute
the unit tests I have provided. Execute these commands:

                cd $COMPOSER_ROOT/developerWorks/perishable-network
                npm install && npm test

Here’s what is going on: First you navigate to the directory where the
perishable-network code lives. Then you run npm install,
which sets up the local Node.js environment (that is, local to the
perishable-network). Then you run npm test,
which executes the unit tests that are part of the project (see
package.json).

You’ll see lots of output, but the last of it looks like this, indicating
the tests were successful:

                    .
                    .
                  Perishable Shipping Network
                    #shipment
                Adding temperature 4.5 to shipment SHIP_001
                Received at: Wed Nov 01 2017 10:58:12 GMT-0500 (CDT)
                Contract arrivalDateTime: Thu Nov 02 2017 10:58:12 GMT-0500 (CDT)
                Lowest temp reading: 4.5
                Highest temp reading: 4.5
                Payout: 2500
                Grower: farmer@email.com new balance: 2500
                Importer: supermarket@email.com new balance: -2500
                      ✓ should receive base price for a shipment within temperature range (90ms)
                Adding temperature 1 to shipment SHIP_001
                Received at: Wed Nov 01 2017 10:58:12 GMT-0500 (CDT)
                Contract arrivalDateTime: Thu Nov 02 2017 10:58:12 GMT-0500 (CDT)
                Lowest temp reading: 1
                Highest temp reading: 4.5
                Min temp penalty: 0.2
                Payout: 1500
                Grower: farmer@email.com new balance: 4000
                Importer: supermarket@email.com new balance: -4000
                      ✓ should apply penalty for min temperature violation (81ms)
                Adding temperature 11 to shipment SHIP_001
                Received at: Wed Nov 01 2017 10:58:12 GMT-0500 (CDT)
                Contract arrivalDateTime: Thu Nov 02 2017 10:58:12 GMT-0500 (CDT)
                Lowest temp reading: 1
                Highest temp reading: 11
                Min temp penalty: 0.2
                Max temp penalty: 0.30000000000000004
                Payout: 999.9999999999998
                Grower: farmer@email.com new balance: 5000
                Importer: supermarket@email.com new balance: -5000
                      ✓ should apply penalty for max temperature violation (74ms)
                
                  3 passing (1s)

Refine the Perishable Goods business
network

The Perishable Goods network models a business network that includes: a
Grower, a Shipper, and an Importer. The specifics are spelled out in the
README.md file. The agreement among the various participants in
this network is modeled using the CTO modeling language, and enforced by
the chaincode (smart contract) written in JavaScript.

In this theoretical exercise in this section, an IoT GPS sensor has been
added to the cargo containers to provide the container ship’s location.
You have been asked to add readings taken by this sensor to the network
model, and send an event when the ship reaches its destination.

To add the GPS sensor to the network model, you’ll need to make changes to
the business model, and write additional unit tests. Instead of writing
the unit tests in Mocha, I’ll show you how to write them using a tool
called Cucumber that has a more human-readable syntax and is just as
powerful.

Modify the network definition

When you have made the changes in this section by pasting in the code
I’ve provided, your solution should look just like the one in the
developerWorks/iot-perishable-network directory. Feel
free to use this model as a reference.

To add the GPS sensor, you need to make a few changes to your model. Start
VSCode, and open the root directory of the Perishable Goods network
($COMPOSER_ROOT)/developerWorks/perishable-network). Open the
model file, called perishable.cto, located in the
models directory.

Add a new enum to represent the major locations on the compass
just below the enum ShipmentStatus:

                /**
                 * Directions of the compass
                 */
                enum CompassDirection {
                  o N
                  o S
                  o E
                  o W
                }

The directions are N for North, S for South, and
so on. It is important to constrain the data entered for a set of GPS
coordinates, and this enum is used to constrain the values
that can be entered into the model to ensure they are valid.

Each time a GPS reading is taken, it is recorded to the blockchain as a
transaction, which means you need to add a transaction to the model for
it. Just below the TemperatureReading transaction, add a new
GpsReading transaction:

Listing 1. Transaction to handle recording GPS
readings in the
blockchain
                /**
                 * A GPS reading for a shipment. E.g. received from a device
                 * within a shipping container
                 */
                transaction GpsReading extends ShipmentTransaction {
                  o String readingTime
                  o String readingDate
                  o String latitude
                  o CompassDirection latitudeDir
                  o String longitude
                  o CompassDirection longitudeDir
                }

A GPS reading requires several parameters that include when the reading was
taken, along with the latitude and longitude. This information is provided
as parameters to the transaction.

Next, in order for the transaction to store the GPS reading in the
blockchain, the information needs to be a part of a blockchain asset.
Because a GPS reading taken from the shipping container is conceptually
part of a shipment, it’s a natural fit to add the readings to the
Shipment asset (just like the
TemperatureReadings). Add the highlighted line (line 7) below
to the Shipment asset:

                asset Shipment identified by shipmentId {
                  o String shipmentId
                  o ProductType type
                  o ShipmentStatus status
                  o Long unitCount
                  o TemperatureReading[] temperatureReadings optional
                  o GpsReading[] gpsReadings optional
                  --> Contract contract
                }

Finally, add two events to the model: one when a temperature threshold is
violated, and another when the container ship has reached its destination
port:

Listing 2. New events – when temperature in the container
exceeds the contractual tolerance, and when the container ship arrives
in
port
                /**
                 * An event - when the temperature goes outside the agreed-upon boundaries
                 */
                event TemperatureThresholdEvent {
                  o String message
                  o Double temperature
                  --> Shipment shipment
                }
                
                /**
                 * An event - when the ship arrives at the port
                 */
                event ShipmentInPortEvent {
                  o String message
                  --> Shipment shipment
                }

You modeled the GPS sensor and the transaction to add GPS readings to the
model. Now you need to write the JavaScript chaincode that handles
updating the blockchain. Open lib/logic.js. You’ll need to
make several changes to this file.

First, add code to the temperatureReading function, which
handles the TemperatureReading transaction. Replace the
entire body of the method with the one below (the lines that are added are
highlighted just for reference):

                function temperatureReading(temperatureReading) {
                
                    var shipment = temperatureReading.shipment;
                    var NS = "org.acme.shipping.perishable";
                    var contract = shipment.contract;
                    var factory = getFactory();
                
                    console.log('Adding temperature ' + temperatureReading.centigrade + ' to shipment ' + shipment.$identifier);
                
                    if (shipment.temperatureReadings) {
                        shipment.temperatureReadings.push(temperatureReading);
                    } else {
                        shipment.temperatureReadings = [temperatureReading];
                    }
                
                    if (temperatureReading.centigrade < contract.minTemperature ||
                        temperatureReading.centigrade > contract.maxTemperature) {
                        var temperatureEvent = factory.newEvent(NS, 'TemperatureThresholdEvent');
                        temperatureEvent.shipment = shipment;
                        temperatureEvent.temperature = temperatureReading.centigrade;
                        temperatureEvent.message = 'Temperature threshold violated! Emitting TemperatureEvent for shipment: ' + shipment.$identifier;
                        emit(temperatureEvent);
                    }
                
                    return getAssetRegistry(NS + '.Shipment')
                        .then(function (shipmentRegistry) {
                            // add the temp reading to the shipment
                            return shipmentRegistry.update(shipment);
                        });
                }

This code checks the current temperature reading against the contractual
stipulations, and if either the min or max temperature limits have been
exceeded, a TemperatureThresholdEvent event is emitted.

Next, add a new function to handle the GpsReading transaction.

Note: it is important that you add the comment block as well.
It contains two important annotations (@param and
@transaction) that you will need
.

                /**
                 * A GPS reading has been received for a shipment
                 * @param {org.acme.shipping.perishable.GpsReading} gpsReading - the GpsReading transaction
                 * @transaction
                 */
                function gpsReading(gpsReading) {
                
                    var factory = getFactory();
                    var NS = "org.acme.shipping.perishable";
                    var shipment = gpsReading.shipment;
                    var PORT_OF_NEW_YORK = '/LAT:40.6840N/LONG:74.0062W';
                    
                    var latLong = '/LAT:' + gpsReading.latitude + gpsReading.latitudeDir + '/LONG:' +
                        gpsReading.longitude + gpsReading.longitudeDir;
                    
                    if (shipment.gpsReadings) {
                        shipment.gpsReadings.push(gpsReading);
                    } else {
                        shipment.gpsReadings = [gpsReading];
                    }
                
                    if (latLong == PORT_OF_NEW_YORK) {
                        var shipmentInPortEvent = factory.newEvent(NS, 'ShipmentInPortEvent');
                        shipmentInPortEvent.shipment = shipment;
                        var message = 'Shipment has reached the destination port of ' + PORT_OF_NEW_YORK;
                        shipmentInPortEvent.message = message;
                        emit(shipmentInPortEvent);
                    }
                
                    return getAssetRegistry(NS + '.Shipment')
                    .then(function (shipmentRegistry) {
                        // add the temp reading to the shipment
                        return shipmentRegistry.update(shipment);
                    });
                }

The chaincode for the transaction stores this GPS reading in the array of
GpsReadings in the Shipment asset. It then
checks to see if this GPS reading corresponds to the destination port, and
if so, emits a ShipmentInPort event. Finally, the blockchain
is updated with the current state of the Shipment.

Add Cucumber feature tests

Let’s sum up. You added a transaction to the model, and two new events. Now
it’s time to unit test the changes to make sure they work. The Hyperledger Composer team recommends using Cucumber to unit test
Composer business models.

Create a new file in the features folder called
iot-perishable.feature and open it in VSCode. I’ll explain
briefly each of the sections you need to add. In the “Unit
testing with Cucumber
” section, I’ll explain Cucumber more
thoroughly. But let’s get something working first.

First, you tell Cucumber about the feature you want to test, along with any
background (setup) that needs to be performed before each unit test. Add
the following code to your empty iot-perishable.feature
file.

Listing 3. Cucumber feature and
background
                Feature: IoT Perishable Network
                
                    Background:
                        Given I have deployed the business network definition ..
                        And I have added the following participants
                        """
                        [
                        {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":0},
                        {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":0},
                        {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
                        ]
                        """
                        And I have added the following asset of type org.acme.shipping.perishable.Contract
                            | contractId | grower           | shipper               | importer           | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                            | CON_001    | grower@email.com | supermarket@email.com | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              | 
                        
                        And I have added the following asset of type org.acme.shipping.perishable.Shipment
                            | shipmentId | type    | status     | unitCount | contract |
                            | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                        When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                            | shipment | centigrade |
                            | SHIP_001 | 4          |
                            | SHIP_001 | 5          |
                            | SHIP_001 | 10         |

Now you need to add some unit tests, called Scenarios. Add the Scenario
below just after the Background block in the
iot-perishable.feature file.

Listing 4. Scenario: when temperature range is within the
agreed-upon
boundaries
        Scenario: When the temperature range is within the agreed-upon boundaries
            When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                | shipment |
                | SHIP_001 |
            
            Then I should have the following participants
            """
            [
            {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":2500},
            {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-2500},
            {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
            ]
            """

This Scenario makes sure that the Grower is paid the full amount when the
temperature in the cargo container is within the agreed-upon limits.

Now add a scenario where the min temperature threshold of 2 degrees Celsius
is violated by 2 degrees.

Listing 5. Scenario: When the low/min
temperature threshold is breached by 2 degrees
C
                Scenario: When the low/min temperature threshold is breached by 2 degrees C
                    Given I submit the following transaction of type org.acme.shipping.perishable.TemperatureReading
                        | shipment | centigrade |
                        | SHIP_001 | 0          |
            
                    When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                        | shipment |
                        | SHIP_001 |
                    
                    Then I should have the following participants
                    """
                    [
                    {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":500},
                    {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-500},
                    {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
                    ]
                    """

In this Scenario, the min temperature drops to zero degrees Celsius, and
the Grower is penalized for each degree Celsius below

the threshold.

Now add a Scenario where the max temperature threshold is exceeded by 2
degrees Celsius.

Listing 6. Scenario: When the hi/max temperature threshold is
breached by 2 degrees
C
                Scenario: When the hi/max temperature threshold is breached by 2 degrees C
                    Given I submit the following transaction of type org.acme.shipping.perishable.TemperatureReading
                        | shipment | centigrade |
                        | SHIP_001 | 12          |
            
                    When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                        | shipment |
                        | SHIP_001 |
                    
                    Then I should have the following participants
                    """
                    [
                    {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":1500},
                    {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-1500},
                    {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
                    ]
                    """

In this Scenario, the max temperature goes above 10 degrees Celsius, and
the Grower is penalized by each degree Celsius above

the threshold.

Finally, add three scenarios (two TemperatureThresholdEvents
and one ShipmentInPortEvent) to the model as shown in Listing
2.

Listing 7. Scenario:
Events
                Scenario: Test TemperatureThresholdEvent is emitted when the min temperature threshold is violated
                    When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                        | shipment | centigrade |
                        | SHIP_001 | 0          |
                    
                    Then I should have received the following event of type org.acme.shipping.perishable.TemperatureThresholdEvent
                        | message                                                                          | temperature | shipment |
                        | Temperature threshold violated! Emitting TemperatureEvent for shipment: SHIP_001 | 0           | SHIP_001 |
                
            
                Scenario: Test TemperatureThresholdEvent is emitted when the max temperature threshold is violated
                    When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                        | shipment | centigrade |
                        | SHIP_001 | 11         |
                    
                    Then I should have received the following event of type org.acme.shipping.perishable.TemperatureThresholdEvent
                        | message                                                                          | temperature | shipment |
                        | Temperature threshold violated! Emitting TemperatureEvent for shipment: SHIP_001 | 11          | SHIP_001 |
                
            
                Scenario: Test ShipmentInPortEvent is emitted when GpsReading indicates arrival at destination port
                    When I submit the following transaction of type org.acme.shipping.perishable.GpsReading
                        | shipment | readingTime | readingDate | latitude | latitudeDir | longitude | longitudeDir |
                        | SHIP_001 | 120000      | 20171025    | 40.6840  | N           | 74.0062   | W            |
            
                    Then I should have received the following event of type org.acme.shipping.perishable.ShipmentInPortEvent
                        | message                                                                           | shipment |
                        | Shipment has reached the destination port of /LAT:40.6840N/LONG:74.0062W | SHIP_001 |

Finally, you need to modify package.json so that it looks like
the one below. I’ve highlighted the lines you need to modify (line 18) and
add (lines 41 and 42).

                {
                  "engines": {
                    "composer": "^0.15.0"
                  },
                  "name": "perishable-network",
                  "version": "0.1.11",
                  "description": "Shipping Perishable Goods Business Network",
                  "networkImage": "http://bit.ly/2khCrXk",
                  "networkImageanimated": "http://bit.ly/2BCrcwO",
                  "scripts": {
                    "prepublish": "mkdirp ./dist && composer archive create  --sourceType dir --sourceName . -a ./dist/perishable-network.bna",
                    "pretest": "npm run lint",
                    "lint": "eslint .",
                    "postlint": "npm run licchk",
                    "licchk": "license-check",
                    "postlicchk": "npm run doc",
                    "doc": "jsdoc --pedantic --recurse -c jsdoc.json",
                    "test": "mocha -t 0 --recursive && cucumber-js",
                    "deploy": "./scripts/deploy.sh"
                  },
                  "repository": {
                    "type": "git",
                    "url": "http://bit.ly/2ki8TIY"
                  },
                  "keywords": [
                    "shipping",
                    "goods",
                    "perishable",
                    "composer",
                    "composer-network",
                    "iot"
                  ],
                  "author": "Hyperledger Composer",
                  "license": "Apache-2.0",
                  "devDependencies": {
                    "browserfs": "^1.2.0",
                    "chai": "^3.5.0",
                    "composer-admin": "^0.14.0-0",
                    "composer-cli": "^0.14.0-0",
                    "composer-client": "^0.14.0-0",
                    "composer-connector-embedded": "^0.14.0-0",
                    "composer-cucumber-steps": "^0.14.0-0",
                    "cucumber": "^2.2.0",
                    "eslint": "^3.6.1",
                    "istanbul": "^0.4.5",
                    "jsdoc": "^3.4.1",
                    "license-check": "^1.1.5",
                    "mkdirp": "^0.5.1",
                    "mocha": "^3.2.0",
                    "moment": "^2.17.1"
                  },
                  "license-check-config": {
                    "src": [
                      "**/*.js",
                      "!./coverage/**/*",
                      "!./node_modules/**/*",
                      "!./out/**/*",
                      "!./scripts/**/*"
                    ],
                    "path": "header.txt",
                    "blocking": true,
                    "logInfo": false,
                    "logError": true
                  }
                }

Notice you added two new Node modules (lines 41 and 42 above) to
package.json. To install them, go to the command line
(Ubuntu) or open a terminal window (MacOS) and run the
npm install command.

Now run the unit tests by executing npm test from the command
line. There will be lots of output, but this time, in addition to the
Mocha tests, you will see one block of output for each Cucumber feature
Scenario. The Cucumber feature tests start out like this (I’m just showing
the first one for space considerations).

                Feature: IoT Perishable Network
                
                  Scenario: When the temperature range is within the agreed-upon boundaries
                  ✔ Given I have deployed the business network definition ..
                  ✔ And I have added the following participants
                      """
                      [
                      {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":0},
                      {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":0},
                      {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
                      ]
                      """
                  ✔ And I have added the following asset of type org.acme.shipping.perishable.Contract
                      | contractId | grower           | shipper               | importer              | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                      | CON_001    | grower@email.com | supermarket@email.com | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              |
                  ✔ And I have added the following asset of type org.acme.shipping.perishable.Shipment
                      | shipmentId | type    | status     | unitCount | contract |
                      | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                  ✔ When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                      | shipment | centigrade |
                      | SHIP_001 | 4          |
                      | SHIP_001 | 5          |
                      | SHIP_001 | 10         |
                  ✔ When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
                      | shipment |
                      | SHIP_001 |
                  ✔ Then I should have the following participants
                      """
                      [
                      {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":2500},
                      {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-2500},
                      {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0}
                      ]
                      """

The last few lines of output look like this, indicating the unit tests
pass.

                    .
                    .
                  ✔ And I have added the following asset of type org.acme.shipping.perishable.Shipment
                      | shipmentId | type    | status     | unitCount | contract |
                      | SHIP_001   | BANANAS | IN_TRANSIT | 5000      | CON_001  |
                  ✔ When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
                      | shipment | centigrade |
                      | SHIP_001 | 4          |
                      | SHIP_001 | 5          |
                      | SHIP_001 | 10         |
                  ✔ When I submit the following transaction of type org.acme.shipping.perishable.GpsReading
                      | shipment | readingTime | readingDate | latitude | latitudeDir | longitude | longitudeDir |
                      | SHIP_001 | 120000      | 20171025    | 40.6840  | N           | 74.0062   | W            |
                  ✔ Then I should have received the following event of type org.acme.shipping.perishable.ShipmentInPortEvent
                      | message                                                                  | shipment |
                      | Shipment has reached the destination port of /LAT:40.6840N/LONG:74.0062W | SHIP_001 |
                
                6 scenarios (6 passed)
                44 steps (44 passed)
                0m02.788s
                $

A closer look at unit testing with Cucumber

Okay, I know I threw a lot at you in the previous section. So let me take a
minute and explain it in more detail, starting with an overview of
Cucumber.

Cucumber overview

Cucumber is a unit testing tool designed for Behavior Driven Development (BDD), a style of development related
to Test-Driven Development (TDD), but more focused on the behavior of the
application, rather than just the functional correctness of the code. It’s
a subtle yet important difference.

You write Cucumber test scenarios in a language called Gherkin. You’ve
already seen it in the previous section. The syntax is fairly simple:

                Feature : The application feature I want to test
                  Background:
                    Given some pre-condition that applies to each test
                
                  Scenario: The test I want to run
                      Given something
                      And some other thing
                      When I do XYZ
                      Then ABC is the expected outcome

A step definition is a small piece of code that links a
pattern in the Gherkin plain language text to
actual code that runs the test. You are responsible
for writing the step
definitions
, or your tests will not run.

When Cucumber
runs
across one of the following keywords: When,
Given, Then, And, or
But, it looks for a step definition to associate the text
with that follows the keyword. By the way, Cucumber makes no distinction
between the keywords; they are all just steps to Cucumber. In other words,
to Cucumber, When ABC and Given ABC both mean
“locate and execute a step with pattern ABC.”

For example, when Cucumber sees
Given I have added the following participants, it looks for a
step whose pattern matches I have added the following, and if
it finds one, it executes it. If not, you must provide it.

The good news is the Hyperledger Composer team has already provided steps
for most of the common usages of the Composer client API. This means that
you can write Cucumber tests in the Gherkin language just like those
you’ve already seen without writing any code yourself! The code is
contained in the JavaScript library composer-cucumber-steps
that you added to package.json in the previous section.

Working with Cucumber

So let’s walk through one of the scenarios. I’ll describe what Cucumber is
doing and show you the actual JavaScript code that runs for the steps from
the composer-cucumber-steps Node.js module.

Refer to Listing 8. In this scenario, there are three keywords:

  • Given I submit the following transaction
    of type org.acme.shipping.perishable.TemperatureReading
  • When I submit the following transaction
    of type org.acme.shipping.perishable.ShipmentReceived
  • Then I should have the following
    participants

Cucumber makes no distinction between the three keywords (they are just
steps), but the patterns following the keywords must match one (and only
one) step. Otherwise, the test will not run, because there is no code to
run it.

In the first instance, Cucumber looks for a pattern (that is, a regular
expression) that matches “I submit the following transaction of type
org.acme.shipping.perishable.TemperatureReading” and finds one. The JavaScript function that runs when this step
executes looks like this:

Listing 8. composer-cucumber-steps/lib/transactionsteps.js
                'use strict';
                
                module.exports = function () {
                
                    this.When(/^I submit the following transactions? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.submitTransactions(namespace, name, table);
                    });
                
                    this.When(/^I submit the following transactions?$/, function (docString) {
                        return this.composer.submitTransactions(null, null, docString);
                    });
                
                };

Wait. Where’s the this.Given() function, right? The functions
shown above all start with this.When. How does Cucumber know
to invoke one of the functions above when your Gherkin contains
Given? Remember, Cucumber doesn’t distinguish between any of
the step keywords (Given, When, And, and so forth). It only
matches the pattern. In fact, if you code a Cucumber step definition with
the same pattern for both Given and When,
Cucumber will report a duplicate step definition error!

The pattern to match is the regular expression between the /
characters in parentheses, including the capture groups
(([.\w]+)\.(\w+)), which are used to parse the arguments that
are passed to the function that gets invoked: namespace
(org.acme.shipping.perishable), name
(TemperatureReading), and table (I’ll talk more
about the table argument shortly).

The code that executes for the step that matches the pattern “I should have
the following participants” matches
this code:

                'use strict';
                
                module.exports = function () {
                
                    this.Given(/^I have added the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.addParticipants(namespace, name, table);
                    });
                
                    this.Given(/^I have added the following participants?$/, function (docString) {
                        return this.composer.addParticipants(null, null, docString);
                    });
                
                    this.When(/^I add the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.addParticipants(namespace, name, table);
                    });
                
                    this.When(/^I add the following participants?$/, function (docString) {
                        return this.composer.addParticipants(null, null, docString);
                    });
                
                    this.When(/^I update the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.updateParticipants(namespace, name, table);
                    });
                
                    this.When(/^I update the following participants?$/, function (docString) {
                        return this.composer.updateParticipants(null, null, docString);
                    });
                
                    this.When(/^I remove the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.removeParticipants(namespace, name, table);
                    });
                
                    this.When(/^I remove the following participants?$/, function (docString) {
                        return this.composer.removeParticipants(null, null, docString);
                    });
                
                    this.Then(/^I should have the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.testParticipants(namespace, name, table);
                    });
                
                    this.Then(/^I should have the following participants?$/, function (docString) {
                        return this.composer.testParticipants(null, null, docString);
                    });
                
                    this.Then(/^I should not have the following participants? of type ([.\w]+)\.(\w+)$/, function (namespace, name, table) {
                        return this.composer.testNoParticipants(namespace, name, table);
                    });
                
                    this.Then(/^I should not have the following participants?$/, function (docString) {
                        return this.composer.testNoParticipants(null, null, docString);
                    });
                
                };

As you can see, the code for steps related to Participants handles much
more than just “I should have”, such as “I remove”, “I add”, and lots
more.

Complex objects in Gherkin

You may have noticed there were two ways to represent objects from the
Perishable Goods network model in the Gherkin examples. First, like
this:

                | contractId | grower           | shipper               | importer              | arrivalDateTime  | unitPrice | minTemperature | maxTemperature | minPenaltyFactor | maxPenaltyFactor |
                | CON_001    | grower@email.com | supermarket@email.com | supermarket@email.com | 10/26/2018 00:00 | 0.5       | 2              | 10             | 0.2              | 0.1              |

Second, like this:

                """
                [
                {"$class":"org.acme.shipping.perishable.Grower", "email":"grower@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"USA"}, "accountBalance":1500},
                {"$class":"org.acme.shipping.perishable.Importer", "email":"supermarket@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":-1500}
                ]
                """

The first type of object representation is called a data table,
and it’s a pipe-delimited tuple, where the first line contains the
property names, followed by one or more lines that contain the property
values. The data table does not support nested objects. When you use a
data table to represent your objects, you have to tell the step the type
of object that follows. The object in this example is a
org.acme.shipping.perishable.Contract object, and it was
specified on the same line as the step (see Listing
2
). Use a data table when your objects do not contain other
required objects from the model (an optional object can be omitted).

The second type uses type a docstring, which is a JSON
representation of the object, and supports nested objects (for example,
notice the nested Address object). When you use a docstring,
you do not have to specify the object type in the step because you must do
so as part of the docstring (see Listing 5).

Use docstring syntax when an object you are trying to represent contains a
nested, required object, or when you want to provide data values for a
nested optional object.

Deploy your modified network to the IBM Cloud

Now that you have modified the network and unit tested it, it’s time to
load it up into the Online Playground and interact with it on the IBM
Cloud at http://bit.ly/2kietLv.

In Part 1, I showed you how to work with Playground to define a
network using the Perishable Goods boilerplate model, instantiate the
model, and submit transactions against the in-memory blockchain. In this
section, you’ll use the Perishable Goods network Business Network Archive
(BNA) file created by the build (that is, npm install) to
deploy the network using Playground’s import feature. But first you need
to make a few code changes and then run a build to create the BNA
file.

Make sure the Perishable Goods network model that you refined earlier in
this tutorial is open in VSCode. Now open lib/logic.js in the
editor window. Add a console log message to the temperatureReading()
function as shown in line 22:

Listing 9. logic.js
temperatureReading()
                function temperatureReading(temperatureReading) {
                
                    var shipment = temperatureReading.shipment;
                    var NS = 'org.acme.shipping.perishable';
                    var contract = shipment.contract;
                    var factory = getFactory();
                
                    console.log('Adding temperature ' + temperatureReading.centigrade + ' to shipment ' + shipment.$identifier);
                
                    if (shipment.temperatureReadings) {
                        shipment.temperatureReadings.push(temperatureReading);
                    } else {
                        shipment.temperatureReadings = [temperatureReading];
                    }
                
                    if (temperatureReading.centigrade < contract.minTemperature ||
                        temperatureReading.centigrade > contract.maxTemperature) {
                        var temperatureEvent = factory.newEvent(NS, 'TemperatureThresholdEvent');
                        temperatureEvent.shipment = shipment;
                        temperatureEvent.temperature = temperatureReading.centigrade;
                        temperatureEvent.message = 'Temperature threshold violated! Emitting TemperatureEvent for shipment: ' + shipment.$identifier;
                        console.log(message);
                        emit(temperatureEvent);
                    }

Now locate the gpsReading() function and add a console log
message as shown in line 22:

                function gpsReading(gpsReading) {
                    
                       var factory = getFactory();
                       var NS = "org.acme.shipping.perishable";
                       var shipment = gpsReading.shipment;
                       var PORT_OF_NEW_YORK = '/LAT:40.6840N/LONG:74.0062W';
                        
                       var latLong = '/LAT:' + gpsReading.latitude + gpsReading.latitudeDir + '/LONG:' +
                           gpsReading.longitude + gpsReading.longitudeDir;
                        
                       if (shipment.gpsReadings) {
                           shipment.gpsReadings.push(gpsReading);
                       } else {
                           shipment.gpsReadings = [gpsReading];
                       }
                    
                       if (latLong == PORT_OF_NEW_YORK) {
                           var shipmentInPortEvent = factory.newEvent(NS, 'ShipmentInPortEvent');
                           shipmentInPortEvent.shipment = shipment;
                           var message = 'Shipment has reached the destination port of ' + PORT_OF_NEW_YORK;
                           shipmentInPortEvent.message = message;
                           console.log(message);
                           emit(shipmentInPortEvent);
                       }
                    
                       return getAssetRegistry(NS + '.Shipment')
                       .then(function (shipmentRegistry) {
                           // add the temp reading to the shipment
                           return shipmentRegistry.update(shipment);
                       });
                   }

Now when you run the TemperatureReading and
GpsReading transactions and they emit events, you can see
them in the JavaScript console.

Create the Business Network Archive

Go to the command line (Ubuntu) or open a terminal window (MacOS), navigate
to the perishable-network directory, which is located in your
$COMPOSER_ROOT directory, and run a build to create the BNA
file by executing npm install from the command line. You’ll
see output like this:

                $ cd $COMPOSER_ROOT
                $ pwd
                /Users/sperry/HyperledgerComposer
                $ cd developerWorks/perishable-network/
                $ npm install
                
                > perishable-network@0.1.11 prepublish /Users/sperry/HyperledgerComposer/developerWorks/perishable-network
                > mkdirp ./dist && composer archive create  --sourceType dir --sourceName . -a ./dist/perishable-network.bna
                
                Creating Business Network Archive
                
                
                Looking for package.json of Business Network Definition
                	Input directory: /Users/sperry/HyperledgerComposer/developerWorks/perishable-network
                
                Found:
                	Description: Shipping Perishable Goods Business Network
                	Name: perishable-network
                	Identifier: perishable-network@0.1.11
                
                Written Business Network Definition Archive file to 
                	Output file: ./dist/perishable-network.bna
                
                Command succeeded

Import the model into the Online Playground on
the IBM Cloud

Now go to the Online Playground on the IBM Cloud:
http://bit.ly/2kietLv
. You should see the (by
now) familiar Welcome screen:

Figure 4. Playground Welcome screen on the IBM Cloud

IBM Cloud Composer Online Playground Welcome screen

If you don’t see the Welcome screen, clear your browser’s local storage. In
Chrome, for example, under Settings > Advanced > Content
Settings > Cookies > All cookies
and site data
> localhost
, click the trashcan icon to remove local
storage. If you’re using a different browser, follow the instructions
specific to that browser, and delete all local storage.

Click the Let’s Blockchain button to get started. When you
see the My Business Networks view, click Deploy a new business
network
.

Figure 5. My Business Networks

IBM Cloud Composer Online Playground - My Business Networks

On the next screen, click Drop here to upload or browse. A
File dialog opens. Navigate to the
$COMPOSER_ROOT/developerWorks/perishable-network/dist
directory, locate the file called perishable-network.bna, and
click Open.

Figure 6. Deploy new business network

IBM Cloud Composer Online Playground - Deploy new business network

The title of the tile you just clicked on changes to
perishable-network. Click the Deploy
button to deploy the model.

Figure 7. Deploy new business network

IBM Cloud Composer Online Playground - Deploy new business network

Now the model is deployed to the Online Playground in the IBM Cloud.

Interacting with the model should be familiar from Part 1 of this series. In fact, the Online
Playground is an IBM Cloud version of the local Playground you used in
Part 1, so I won’t go over how to interact with your model again. Please
review Part 1 if you need a refresher.

In the section titled “Refine the Perishable Goods
business network
,” you ran unit tests to verify that the code in
the new model works. Now you will interact with the model through the
Online Playground to test those same transactions and verify them using
the JavaScript console. Click Connect now on the Admin
card to begin.

First, instantiate the model by clicking the Test tab just
below the address bar in the browser. Click the Submit
Transaction
button, then select the SetupDemo transaction,
and click Submit.

Now open the JavaScript console. How you do this depends on your browser.
If you’re using Chrome, select View > Developer > JavaScript
console
.

Next, submit a TemperatureReading transaction for
Shipment SHIP_001 of 11 degrees C, which is above the max
temperature threshold and will trigger a
TemperatureThresholdEvent, as shown in Figure 8.

Figure 8. Submit TemperatureReading transaction

IBM Cloud Composer Online Playground - Submit TemperatureReading                     transaction

You should see the following message (among many others, so you may need to
look carefully) in the JavaScript console:
Temperature threshold violated! Emitting TemperatureEvent for
shipment: SHIP_001
.
This indicates that the event is emitted properly (though we already knew
that from running the Cucumber unit test successfully earlier, didn’t
we?).

Now submit a GpsReading transaction for Shipment
SHIP_001 with the following parameters, as shown in Figure 9.

  • readingTime = 171000
  • readingDate = 20171031
  • latitude = 40.6840
  • latitudeDir = N
  • longitude = 74.0062
  • longitudeDir = W
Figure 9. Submit GpsReading transaction

IBM Cloud Composer Online Playground - Submit GpsReading                     transaction

You should see the following message in the JavaScript console:
Shipment has reached the destination port of /LAT:40.6840N/LONG:74.0062W.

Congratulations! You have a working model running in the IBM Cloud.

Conclusion to Part 2

In this tutorial, you set up the tools for local Hyperledger Composer
development on your computer. You modified the Perishable Goods network
and added a new transaction and two new events, and then unit tested those
events with Cucumber.

Then you built a Composer Business Network Archive (BNA) file from the
modified Perishable Goods network and deployed and tested it in the Online
Playground hosted on the IBM Cloud.

Stay tuned for Part 3, where you’ll install more tools to really harness
the power of Hyperledger Composer by generating a REST interface. You’ll
install and run Hyperledger Fabric on your computer, and generate a GUI
that you can use to interact with the Perishable Goods network running on
your computer just as it would on a production server. No more Playground
in Part 3!


Downloadable resources



#awvi,#blockchain,#ibm

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

December 1, 2017 at 06:15AM