Tutorial
By J Steven Perry | Published January 1, 2017
Blockchain
Note: IBM Blockchain Platform now utilizes Hyperledger Fabric for end-to-end development. Users may use Hyperledger Composer at their choice, but IBM will not provide support for it.
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.
(And when you complete this Part 2 tutorial, then Part 3 wraps everything up by showing you how to install Hyperledger Fabric on your computer, deploy your business network to your local instance, and interact with the sample network blockchain application.)
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.
Get a monthly roundup of the best free tools, training, and community resources to help you put blockchain to work. Recent issues | Subscribe
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 (and native Windows support doesn’t look likely, as this issue appears to have gone nowhere).
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.)
Git is one of the most popular source code repository management tools today. To follow along with the tutorial, you’ll need to install Git. On Ubuntu, installing Git is a snap: sudo apt-get install git and you’re done. If you run MacOS, Git will be installed with the command-line tools for MacOS when you install Node.js in the next section.
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.
The Hyperledger Composer team provides a script to install nvm. To install nvm, execute the following:
curl -O https://hyperledger.github.io/composer/prereqs-ubuntu.sh
Then edit the downloaded script and make sure the latest version of nvm will be downloaded (0.33.11 as I write this). Save the script, make it executable, then run it:
chmod u+x prereqs-ubuntu.sh ./prereqs-ubuntu.sh
Git is one of the most popular source code repository management tools today. To follow along with the tutorial, you’ll need to install git. On Ubuntu, installing git is a snap: sudo apt-get install git and you’re done. If you run MacOS, git will be installed with the command-line tools for MacOS when you install Node.js in the next section.
sudo apt-get install git
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.
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.
git
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- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
Note: Check the README on the nvm GitHub repo to make sure you are installing the latest version (0.33.11 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
$ nvm --version 0.33.11
Now you’re ready to install Node.js using nvm.
To install Node.js, go to the command line (Ubuntu) or open a terminal window (MacOS) to run the nvm install v8 command to install the LTS version of Node.js:
nvm install v8
You’ll see output like this:
$ nvm install v8 Downloading and installing node v8.12.0... Local cache found: $NVM_DIR/.cache/bin/node-v8.12.0-darwin-x64/node-v8.12.0-darwin-x64.tar.xz Checksums match! Using existing downloaded archive $NVM_DIR/.cache/bin/node-v8.12.0-darwin-x64/node-v8.12.0-darwin-x64.tar.xz Now using node v8.12.0 (npm v6.4.1)
Note: You must use Node 8 when installing the Composer tools or you will get an error trying to install grpc.
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.12.0
In this case, I have installed Node.js 8.12.0, which is the latest version 8 at the time of this writing.
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 the 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.
-g
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
$ composer -v v0.20.4
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 we recommend 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.
To install on Ubuntu (or other Debian-based Linux), choose one of these options:
.deb
.rpm
.tar.gz
Pick the method you like best, and follow the detailed installation instructions on the VSCode website.
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.
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.
Hyperledger
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.
perishable.cto
namespace
enum
This video shows you how to set up your environment.
Transcript
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.
perishable-network
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:
~/HyperledgerComposer
$ 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.
$COMPOSER_ROOT
Go to a command line (Ubuntu) or open a terminal window (MacOS), navigate to your $COMPOSER_ROOT directory, and enter the command git clone https://github.com/makotogo/developerWorks.git as shown here:
git clone https://github.com/makotogo/developerWorks.git
$ cd $COMPOSER_ROOT $ pwd /Users/sperry/HyperledgerComposer $ git clone https://github.com/jstevenperry/IBM-Developer Cloning into 'IBM-Developer'... remote: Enumerating objects: 326, done. remote: Counting objects: 100% (326/326), done. remote: Compressing objects: 100% (155/155), done. remote: Total 1236 (delta 112), reused 279 (delta 80), pack-reused 910 Receiving objects: 100% (1236/1236), 21.92 MiB | 12.73 MiB/s, done. Resolving deltas: 100% (498/498), done. $
Now that 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/IBM-Developer/Hyperledger/Composer/Basics 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).
npm install
npm test
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 Dec 31 1969 18:00:00 GMT-0600 (CST) Contract arrivalDateTime: Sun Nov 11 2018 14:09:14 GMT-0600 (CST) 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 Adding temperature 4.5 to shipment SHIP_001 Received at: Thu Sep 26 33658 20:46:40 GMT-0500 (CDT) Contract arrivalDateTime: Sun Nov 11 2018 14:09:14 GMT-0600 (CST) Late shipment Payout: 0 Grower: farmer@email.com new balance: 2500 Importer: supermarket@email.com new balance: -2500 ✓ should receive nothing for a late shipment Adding temperature 1 to shipment SHIP_001 Received at: Wed Dec 31 1969 18:00:00 GMT-0600 (CST) Contract arrivalDateTime: Sun Nov 11 2018 14:09:14 GMT-0600 (CST) 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 Adding temperature 11 to shipment SHIP_001 Received at: Wed Dec 31 1969 18:00:00 GMT-0600 (CST) Contract arrivalDateTime: Sun Nov 11 2018 14:09:14 GMT-0600 (CST) 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 4 passing (1s)
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.
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 IBM-Developer/Hyperledger/Composer/Basics/iot-perishable-network directory. Feel free to use this model as a reference.
IBM-Developer/Hyperledger/Composer/Basics/iot-perishable-network
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)/IBM-Developer/Hyperledger/Composer/Basics/perishable-network). Open the model file, called perishable.cto, located in the models directory.
models
Add a new enum to represent the major locations on the compass just below the enum ShipmentStatus:
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.
N
S
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:
TemperatureReading
GpsReading
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 line 7 below (o GpsReading[] gpsReadings optional) to the Shipment asset:
Shipment
o GpsReading[] gpsReadings optional
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 just below the Importer participant — 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.
lib/logic.js
First, add this line to the top of the file to tell the linter to ignore global functions like getParticipantRegistry():
getParticipantRegistry()
/* global getParticipantRegistry getAssetRegistry getFactory emit */
Next, add code to the temperatureReading function, which handles the TemperatureReading transaction. Replace the entire body of the method with the one below:
temperatureReading
/** * A temperature reading has been received for a shipment * @param {org.acme.shipping.perishable.TemperatureReading} temperatureReading - the TemperatureReading transaction * @transaction */ async function temperatureReading(temperatureReading) { // eslint-disable-line no-unused-vars const NS = 'org.acme.shipping.perishable'; const shipment = temperatureReading.shipment; const contract = shipment.contract; const 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(temperatureEvent.message); emit(temperatureEvent); } // add the temp reading to the shipment const shipmentRegistry = await getAssetRegistry(NS + '.Shipment'); await 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.
TemperatureThresholdEvent
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 that you will need (@param and @transaction).
@param
@transaction
/** * A GPS reading has been received for a shipment * @param {org.acme.shipping.perishable.GpsReading} gpsReading - the GpsReading transaction * @transaction */ async function gpsReading(gpsReading) { // eslint-disable-line no-unused-vars 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); } const shipmentRegistry = await getAssetRegistry(NS + '.Shipment'); await 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.
ShipmentInPort
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.
Create a new file in the features folder called iot-perishable.feature and open it in VSCode. I’ll briefly explain each of the sections you need to add. In the section “A closer look at unit testing with Cucumber,” I’ll explain Cucumber more thoroughly. But let’s get something working first.
features
iot-perishable.feature
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.
ShipmentInPortEvent
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. The easiest thing to do is just replace the entire contents of package.json with the listing below.
{ "engines": { "composer": "^0.20.0" }, "name": "iot-perishable-network", "version": "0.2.6", "description": "IoT Shipping Perishable Goods Business Network", "networkImage": "https://hyperledger.github.io/composer-sample-networks/packages/perishable-network/networkimage.svg", "networkImageanimated": "https://hyperledger.github.io/composer-sample-networks/packages/perishable-network/networkimageanimated.svg", "scripts": { "prepublish": "mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/iot-perishable-network.bna", "pretest": "npm run lint", "lint": "eslint .", "postlint": "npm run licchk", "licchk": "license-check-and-add", "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": "https://github.com/hyperledger/composer-sample-networks.git" }, "keywords": [ "shipping", "goods", "perishable", "composer", "composer-network" ], "author": "Hyperledger Composer", "license": "Apache-2.0", "devDependencies": { "chai": "^3.5.0", "composer-admin": "^0.20.0-0", "composer-cli": "^0.20.0-0", "composer-client": "^0.20.0-0", "composer-common": "^0.20.0-0", "composer-connector-embedded": "^0.20.0-0", "composer-cucumber-steps": "^0.20.0-0", "cucumber": "^2.2.0", "eslint": "^3.6.1", "istanbul": "^0.4.5", "jsdoc": "^3.5.5", "license-check-and-add": "~2.3.0", "mkdirp": "^0.5.1", "mocha": "^3.2.0", "moment": "^2.17.1", "sinon": "2.3.8", "cryptiles": ">=4.1.2", "growl": ">=1.10.0" }, "license-check-and-add-config": { "folder": ".", "license": "LICENSE.txt", "exact_paths_method": "EXCLUDE", "exact_paths": [ "composer-logs", "dist", "node_modules", "out", ".git", "markdown-license.txt", ".DS_Store", "features" ], "file_type_method": "EXCLUDE", "file_types": [ ".yml" ], "insert_license": false, "license_formats": { "js|cto|acl": { "prepend": "/*", "append": " */", "eachLine": { "prepend": " * " } }, "md": { "file": "./markdown-license.txt" } } } }
Notice that you’ve added two new Node modules (lines 42 and 43 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 $
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 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.”
When
Given
Then
And
But
When ABC
Given 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.
Given I have added the following participants
I have added the following
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.
composer-cucumber-steps
Cucumber has excellent documentation. To learn more about Cucumber, visit the Cucumber docs.
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 https://composer-playground.mybluemix.net/.
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.
Go to the command line (Ubuntu) or open a terminal window (MacOS) 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.2.6 prepublish /Users/sperry/HyperledgerComposer/IBM-Developer/Hyperledger/Composer/Basics/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/IBM-Developer/Hyperledger/Composer/Basics/perishable-network Found: Description: Shipping Perishable Goods Business Network Name: perishable-network Identifier: perishable-network@0.2.6 Written Business Network Definition Archive file to Output file: ./dist/perishable-network.bna Command succeeded
Now go to the Online Playground on the IBM Cloud: https://composer-playground.mybluemix.net/. You should see the (by now) familiar Welcome screen:
If you don’t see the Welcome screen, clear your browser’s 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.
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.
$COMPOSER_ROOT/developerWorks/perishable-network/dist
perishable-network.bna
The title of the tile you just clicked on changes to perishable-network. Click the Deploy button to deploy the model.
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.
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.
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:
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.
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.
You then 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.
Now you’re ready 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!
This content was originally published on IBM Developer on November 30, 2017.
Conference
June 7, 2019
São Paulo
BlockchainContainers
May 6, 2019
AnalyticsArtificial Intelligence+
September 23, 2019
Back to top