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. For more on Hyperledger Fabric, please visit https://developer.ibm.com/components/hyperledger-fabric/.

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. In Part 2, you saw how to model an IoT GPS sensor in a shipping container by adding GPS readings to the Shipment asset, and modify the chaincode to send an alert when the Shipment reaches its destination port. Then you deployed and modified the Perishable Goods network to the IBM Cloud in the Composer Online Playground.

Now, in this third and final part, you’ll install Hyperledger Fabric on your computer, and deploy the business network archive (BNA) to an instance of Hyperledger Fabric running on your machine (referred to as your local Hyperledger Fabric). You’ll install more tools and generate a Loopback-based REST interface that you can use to interact with the sample network blockchain application.

Part 3 also includes a detailed discussion of Hyperledger Composer security concepts. This part concludes with steps to pull it all together and extend the iot-perishable-network to create a more “real world” version of the Perishable Goods network.

Prerequisites

I’ll assume that you’ve worked through Part 2, and that you have the following software installed on your computer:

1

Deploy the network to your local Hyperledger Fabric

Hyperledger Fabric is a framework for building blockchain applications for business purposes, and as you have already learned, Hyperledger Composer is a companion tool that makes building blockchain applications that run on Hyperledger Fabric easier. Up to now, you have used Composer Playground in “browser-only” mode (in Part 1) and the Composer Online playground in the IBM Cloud (Part 2).

Now, you’ll install and run the Hyperledger Fabric on your computer, and use the Composer Command Line Interface (CLI) to interact with it.

1a

Get Hyperledger Fabric

First, create a directory on your computer where you want to do your local Hyperledger Composer development. For this tutorial, I’ll refer to this location as $COMPOSER_ROOT in code, and in prose, I’ll refer to it as your Composer root directory. This directory can be anywhere you want, but I recommend you create it just off your home folder, and always set the COMPOSER_ROOT environment variable to that directory in the current shell, since that’s how I’ll refer to that location throughout this tutorial.

Now download the fabric-dev-servers.zip distribution file to your Composer root directory using the curl command, and unzip it. The sequence of commands looks like this (assuming ~/HyperledgerComposer as the root directory for this example):

mkdir ~/HyperledgerComposer
export COMPOSER_ROOT=~/HyperledgerComposer
cd $COMPOSER_ROOT
mkdir fabric-dev-servers && cd fabric-dev-servers
curl -O https://raw.githubusercontent.com/hyperledger/composer-tools/master/packages/fabric-dev-servers/fabric-dev-servers.zip
unzip fabric-dev-servers.zip
1b

Start Hyperledger Fabric

Note: before you continue, make sure Docker is running.

The first time you run Hyperledger Fabric, execute this sequence of commands:

export FABRIC_VERSION=hlfv12
cd $COMPOSER_ROOT/fabric-dev-servers
./downloadFabric.sh
./startFabric.sh
./createPeerAdminCard.sh

It will take several minutes to run the first command, which pulls all of the necessary Docker images. The second command starts the local Hyperledger Fabric. The last command issues an ID card for the fabric admin, which is PeerAdmin. Don’t worry about that for now; I’ll cover ID cards later in the tutorial. The output of the createPeerAdminCard.sh script looks like this:

$ ./createPeerAdminCard.sh
Development only script for Hyperledger Fabric control
Running 'createPeerAdminCard.sh'
FABRIC_VERSION is set to 'hlfv12'
FABRIC_START_TIMEOUT is unset, assuming 15 (seconds)
Using composer-cli at v0.20.4
Successfully created business network card file to
Output file: /tmp/PeerAdmin@hlfv1.card
Command succeeded
Deleted Business Network Card: PeerAdmin@hlfv1
Command succeeded
Successfully imported business network card
Card file: /tmp/PeerAdmin@hlfv1.card
Card name: PeerAdmin@hlfv1
Command succeeded
The following Business Network Cards are available:
Connection Profile: defaultProfile
┌───────────────┬────────┬────────────────────┐
│ Card Name     │ UserId │ Business Network   │
├───────────────┼────────┼────────────────────┤
│ PeerAdminCard │ admin  │                    │
├───────────────┼────────┼────────────────────┤
│ networkAdmin  │ admin  │ perishable-network │
└───────────────┴────────┴────────────────────┘
Connection Profile: hlfv1
┌─────────────────┬───────────┬──────────────────┐
│ Card Name       │ UserId    │ Business Network │
├─────────────────┼───────────┼──────────────────┤
│ PeerAdmin@hlfv1 │ PeerAdmin │                  │
└─────────────────┴───────────┴──────────────────┘
Issue composer card list --card <Card Name> to get details a specific card
Command succeeded
Hyperledger Composer PeerAdmin card has been imported, host of fabric specified as 'localhost'

Make a note of the card name (PeerAdmin@hlfv1 in the output above), because you will need it to execute all of the CLI commands in this tutorial.

To shut down the local Hyperledger Fabric, run the stopFabric.sh script.

For the purposes of this tutorial, I suggest you leave Hyperledger Fabric up for now.

1c

Deploy to Hyperledger Fabric

In Part 2, you cloned the developerWorks project from GitHub and modified the Perishable Goods network. I’ve provided a finished version of that network in the developerWorks/iot-perishable-network directory. You’ll use that network for the rest of this tutorial.

To deploy the iot-perishable-network to your local Fabric, use the Composer CLI and execute this sequence of commands:

cd $COMPOSER_ROOT/IBM-Developer/Hyperledger/Composer/Basics/iot-perishable-network
composer network install --card PeerAdmin@hlfv1 --archiveFile dist/iot-perishable-network.bna
composer network start --networkName iot-perishable-network --networkVersion 0.2.6 --networkAdmin admin --networkAdminEnrollSecret adminpw --card PeerAdmin@hlfv1 --file networkadmin.card
composer card import --file networkadmin.card

The composer network install command installs the specified network archive (dist/iot-perishable-network.bna) to the local Hyperledger Fabric, using the PeerAdmin card you created earlier when you ran the createPeerAdminCard.sh script to authenticate, and the composer network start command starts the network. These two commands, taken together, deploy the business network to the Fabric. When the deployment is finished, an ID card is issued for the network administrator, whose credentials — that is, userid and password (or secret) — are stored in the networkadmin.card file.

The output looks like this:

$ composer network install --card PeerAdmin@hlfv1 --archiveFile dist/iot-perishable-network.bna
 Installing business network. This may take a minute...
Successfully installed business network iot-perishable-network, version 0.2.6

Command succeeded
$ composer network start --networkName iot-perishable-network --networkVersion 0.2.6 --networkAdmin admin --networkAdminEnrollSecret adminpw --card PeerAdmin@hlfv1 --file networkadmin.card
Starting business network iot-perishable-network at version 0.2.6

Processing these Network Admins:
        userName: admin

 Starting business network definition. This may take a minute...
Successfully created business network card:
        Filename: networkadmin.card

Command succeeded

The composer card import command tells Hyperledger Composer to import the specified card file so it can be used later to authenticate the user whose credentials are stored in the card. In this case, the card is for the network admin. I’ll talk more about cards later on. The output looks like this:

$ composer card import --file networkadmin.card

Successfully imported business network card
        Card file: networkadmin.card
        Card name: admin@iot-perishable-network

Command succeeded
2

Interact with the network via the Composer REST interface

You’ve deployed the iot-perishable-network to the local Hyperledger Fabric, but how do you see what’s there? How do you interact with it? Hyperledger Composer provides a tool called composer-rest-server that generates a Loopback-based REST interface to access your network.

2a

Install the Composer REST interface generator

First, you need to install the composer-rest-server loopback generator. Go to a command line (Ubuntu) or open a Terminal window (MacOS) and enter this command:

npm install -g composer-rest-server

When the installation finishes, verify that composer-rest-server was installed correctly by running composer-rest-server -v:

$ composer-rest-server -v
v0.20.4
2b

Generate the REST interface

Make sure the Hyperledger Fabric is running and the iot-perishable-network is deployed before generating the REST interface (or there will be nothing to connect to). From the command line, run the composer-rest-server command. It will prompt you for input. You’ll see something like this:

Listing 1. Starting the REST server

$ composer-rest-server
? Enter the name of the business network card to use: admin@iot-perishable-network
? Specify if you want namespaces in the generated REST API: always use namespaces
? Specify if you want to use an API key to secure the REST API: No
? Specify if you want to enable authentication for the REST API using Passport: No
? Specify if you want to enable the explorer test interface: Yes
? Specify a key if you want to enable dynamic logging:
? Specify if you want to enable event publication over WebSockets: No
? Specify if you want to enable TLS security for the REST API: No

To restart the REST server using the same options, issue the following command:
   composer-rest-server -c admin@iot-perishable-network -n always -u true

Discovering types from business network definition ...
Discovering the Returning Transactions..
Discovered types from business network definition
Generating schemas for all types in business network definition ...
Generated schemas for all types in business network definition
Adding schemas for all types to Loopback ...
Added schemas for all types to Loopback
Web server listening at: http://localhost:3000
Browse your REST API at http://localhost:3000/explorer

The REST server needs to know how to authenticate with Hyperledger Fabric so it can communicate with the business network. You provide an ID card for that such as the admin@iot-perishable-network ID card from earlier in the tutorial.

Namespaces help avoid name collisions. In the iot-perishable-network, this is not a big deal since there’s only a handful of assets, participants, and transactions. I think it’s a good idea to always use namespaces. Testing real-world networks with lots of participants, assets, and transactions is hard enough; using namespaces will help you avoid headaches related to name collisions.

The next time you want to start the REST server with those same options, just use the command shown (line 9) and skip the interview!

2c

Use the REST interface

To use the REST interface, open a browser and point it to the address shown on line 18 in Listing 1. You’ll see something like this:

Figure 1. The REST interface

The REST interface

The interface is fairly intuitive. To work with an object, click it, and when it expands you’ll see the REST methods you can invoke (for example, /GET, /POST, and so on). To invoke the SetupDemo transaction (which instantiates the business model), click the line that contains SetupDemo, which then expands to the POST method. Click the Try it out! button to invoke the transaction. If it succeeds, you’ll see an HTTP 200 response code. Then you can navigate through the model and see the various objects like the Contract, shown in Figure 2.

Figure 2. The REST interface showing Contract object

The REST interface showing the Contract object

In the video below, I’ll show you how to use the REST interface in detail, so be sure to check that out.

2d

Secure the REST interface

This is beyond the scope of this tutorial, but if you plan to run the REST interface in production, you need a strategy to deal with it. An entire tutorial could be written on this topic alone! Fortunately, there are resources on the Hyperledger Composer website to help. Here are some links to the Composer docs:

Video: Using the Composer REST interface

Transcript

3

Set up Hyperledger Composer security

There are two levels of security with Hyperledger Composer:

  • Hyperledger Fabric administrator
  • Business network administrator

The administrator for your local Hyperledger Fabric is the Peer Administrator (or PeerAdmin for short), which you created when you installed the local Hyperledger Fabric. Every business network should have an administrator as well, which is created when the network is deployed by the Hyperledger Fabric administrator. Authentication for both is handled using ID Cards, which you’ll learn about next.

3a

ID Cards

An ID card (or card for short) is a collection of files that contains all the information necessary to allow a participant to connect to a business network. The card is referred to as an identity. Before it can be used, it must be issued to the user, allowing him or her to be authenticated and authorized to use the network. Cards are a very handy way of securing access to a Hyperledger Fabric network. Rather than keeping up with a password (called a secret in Hyperledger Composer terminology), you import the card into a collection of cards in the Hyperledger Fabric called a wallet. From that point on, you can just reference the card to authenticate that identity.

The general form for specifying a card is: userid@network, where userid is the user’s unique id, and the network is the network to which the user is authenticated.

To handle the two levels of Hyperledger Composer security, you need a card for at least: (1) the PeerAdmin and (2) the business network admin.

PeerAdmin

The PeerAdmin card is a special ID card used to administer the local Hyperledger Fabric. In a development installation, such as the one on your computer, the PeerAdmin ID card is created when you install the local Hyperledger Fabric.

The form for a PeerAdmin card for a Hyperledger Fabric v1.0 network is PeerAdmin@hlfv1. You already used this card earlier in the tutorial when you deployed the iot-perishable-network to your local Hyperledger Fabric.

In general, the PeerAdmin is a special role reserved for functions such as:

  • Deploying business networks
  • Creating, issuing, and revoking ID cards for business network admins

As a developer, you would not have access to the PeerAdmin card in a production Hyperledger Fabric installation. Instead the Hyperledger Fabric administrator would deploy your business network, create ID cards, and so on. When developing and testing blockchain networks using your local Hyperledger Fabric, you will use the PeerAdmin ID card to perform these functions.

Business network admin

When the PeerAdmin deploys your network to the Hyperledger Fabric, an ID card is issued to the business network administrator, and then this card is used whenever the business network administrator needs to do anything with the business network, such as using the Composer Command Line Interface (which you’ll use shortly).

Remember the admin@iot-perishable-network card from earlier? That was the business network admin card issued by the PeerAdmin (that is, the local Hyperledger Fabric administrator) when the iot-perishable-network was deployed.

In general, the business network admin is a special role that’s reserved for functions such as:

  • Updating the running the business network
  • Querying the various registries (participant, identity, and so forth)
  • Creating, issuing, and revoking ID cards for participants in the business network

That’s right, the admin ID card can also be used to issue other ID cards for specific participants (I’ll show you how to do that later in the tutorial), so that all participants have their own ID cards. Access to the network can be controlled by these cards.

3b

Access control

Speaking of access control, Composer implements the concept of role-based security through permissions that are baked right into its architecture to handle both authentication and authorization. A participant’s access to resources is controlled based on the identity that has been issued to that participant.

In this section, you’ll see how to set up access control rules to lock down resources in your network by participant through the Access Control List (ACL) file called permissions.acl (at the time of this writing, the file must have this name). Here are the highlights:

  • Access is applied to either grant or deny based on resources that match the rule. By default, if a resource matches no rule, then access to it is denied.
  • The file is processed top-down so that the first rule that either grants or denies access to a particular resource is in effect and cannot be overridden by a subsequent rule.
  • The format of a rule is fairly intuitive. The rule keyword indicates the start of a rule, followed by a rule name, which must be unique.
  • The rule consists of a set of name/value pairs that define the rule’s properties.

Listing 2 shows the permissions.acl file from the iot-perishable-network business network.

Listing 2. permissions.acl from the iot-perishable-network

/**
 * Sample access control list.
 */
rule Default {
    description: "Allow all participants access to all resources"
    participant: "ANY"
    operation: ALL
    resource: "org.acme.shipping.perishable.*"
    action: ALLOW
}

rule SystemACL {
  description:  "System ACL to permit all access"
  participant: "org.hyperledger.composer.system.Participant"
  operation: ALL
  resource: "org.hyperledger.composer.system.**"
  action: ALLOW
}

rule NetworkAdminUser {
    description: "Grant business network administrators full access to user resources"
    participant: "org.hyperledger.composer.system.NetworkAdmin"
    operation: ALL
    resource: "**"
    action: ALLOW
}

rule NetworkAdminSystem {
    description: "Grant business network administrators full access to system resources"
    participant: "org.hyperledger.composer.system.NetworkAdmin"
    operation: ALL
    resource: "org.hyperledger.composer.system.**"
    action: ALLOW
}

The general format for each property is property: "MATCH_EXPRESSION". The properties are:

description— a human-readable name for the rule in double quotes. Example: description: "This is a description"

participant— fully qualified name of the participant to which access is granted or denied, surrounded by double quotes. Multiple participants may be specified in the MATCH_EXPRESSION through the use of the single asterisk (*) wildcard to indicate “all”, double asterisk (**) to indicate recursion within a namespace, or ANY, which matches all participants in all namespaces.

Examples:

  • participant: "org.acme.shipping.perishable.Grower"— Apply the rule to org.acme.shipping.perishable.Grower only.
  • participant: "org.acme.shipping.perishable.*"— Apply the rule to all participants in the org.acme.shipping.perishable namespace.
  • participant: "org.acme.shipping.**"— Apply the rule to all participants in the org.acme.shipping namespace, and recursively any namespaces beneath it.
  • participant: "ANY"— Apply the rule to all participants in all namespaces.

operation— the MATCH_EXPRESSION may be one or more of CREATE, READ, UPDATE, DELETE, or ALL. Multiple values may be separated by commas (for example, CREATE,READ will grant both CREATE and READ access to the resource). Use ALL by itself to indicate the rule applies to all operations.

Examples:

  • operation: ALL— Apply the rule to all CRUD operations.
  • operation: CREATE— Apply the rule only to the CREATE operation.
  • operation: READ,UPDATE— Apply the rule to READ and UPDATE operations.

resource— defines the “thing” to which the rule applies. A resource can be any class (that is, an asset, participant, or transaction) from the business model. Through the use of wildcards, multiple classes can also be specified, in the same way as for participant above.

Examples:

  • resource: "org.acme.shipping.perishable.TemperatureReading"— The rule applies only to the org.acme.shipping.perishable.TemperatureReading transaction.
  • resource: "org.acme.shipping.perishable.*"— The rule applies to all classes in the org.acme.shipping.perishable namespace.
  • resource: "org.acme.shipping.**"— Apply the rule to all resources in the org.acme.shipping namespace, and recursively to any namespaces beneath it.

action— the action that applies when the rule fires. One of: ALLOW to grant access, or DENY to deny the specified participant(s) access to the resource(s).

Examples:

  • action: ALLOW— Allow access to the specified resource(s).
  • action: DENY— Deny access to the specified resource(s).

Note: If your network has no permissions.acl, then access is granted to all participants (that is, access is wide open — there is no resource-level security).

You will see how to use rules later in the tutorial. Also, the Composer ACL documentation has lots of examples, so be sure to check that out.

Issue a new ID – Playground

ID cards can be kind of abstract, and difficult to visualize. So let’s look at one in Playground (in fact, you already have, you just might not have realized it).

Start the Hyperledger Composer Playground and import the iot-perishable-network (don’t forget to create the BNA by running npm install if you haven’t already). You imported a network in Part 2, so check that out if you need a refresher.

Make sure to invoke the SetupDemo transaction to create the participants and store them in the participant registry. Why? Because you cannot issue an ID card for a participant that is not in the participant registry. In order to work with the classes from the business model, you must instantiate the model, and that’s what SetupDemo does for you. Please review the “Test the business network” section from Part 1 of this tutorial series if you want more information on working with business models.

Once the model is instantiated, in the upper right corner where you see admin, click to expand the drop-down and select ID Registry. On the next screen, choose Issue new ID and then enter grower1 as the ID Name, and select farmer@email.com from the drop-down. See Figure 3.

Figure 3. Issue a new ID card in Playground

Issue a new ID Card in Playground

Click the Create New button to issue the ID. Once the grower1 ID card has been issued, it will show up in the ID registry. See Figure 4.

Figure 4. New grower1 ID card in Playground

New grower1 ID card in Playground

I’ll show you how to issue ID cards using the Composer CLI later in the tutorial.

4

Interact with the network via the Composer CLI

I showed you how to install the Composer Command Line Interface (CLI) in Part 2, and you used it earlier in this tutorial to deploy the iot-perishable-network and import the admin@iot-perishable-network card. Now you will use the CLI to interact with the iot-perishable-network business network. The CLI is really handy because it lends itself to use in scripts, and it’s easy to use, as you’ll see. To use the CLI, you’ll need to go to the command line (Ubuntu) or open a terminal window (MacOS).

Ping the network

Once you’ve deployed the iot-perishable-network network, you can send it a ping, which doesn’t have any side effects. It’s a safe way to just see if your network is up and running. Enter this command:

composer network ping --card admin@iot-perishable-network

If the ping subcommand succeeds, you will see output like this:

$ composer network ping --card admin@iot-perishable-network
The connection to the network was successfully tested: iot-perishable-network
        Business network version: 0.2.6
        Composer runtime version: 0.20.4
        participant: org.hyperledger.composer.system.NetworkAdmin#admin
        identity: org.hyperledger.composer.system.Identity#b205b7f301b2b8a7138a1bfbbbe6491cdca5e4590102e06192600d84951f4714

Command succeeded

Invoke the SetupDemo transaction

Note: If you instantiated the model when working with the REST interface, don’t execute the transaction below or you will get an error message (because the model has already been instantiated).

The SetupDemo transaction is used to instantiate the model. Enter this command:

composer transaction submit --card admin@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.SetupDemo"}'

The output looks like this:

$ composer transaction submit --card admin@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 0, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded

Invoke the TemperatureReading transaction

The TemperatureReading transaction is invoked by the IoT sensor in the shipping container each time a temperature reading is taken and needs to be recorded in the blockchain. Enter this command:

composer transaction submit --card admin@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 0, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'

The output looks like this:

$ composer transaction submit --card admin@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 0, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded

Invoke the ShipmentReceived transaction

The ShipmentReceived transaction is invoked by the Importer when the shipment is received. Enter this command:

composer transaction submit --card admin@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.ShipmentReceived", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'

The output looks like this:

$ composer transaction submit --card admin@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.ShipmentReceived", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded

Be sure to check out the CLI documentation for a complete reference of commands, examples, and lots more.

5

Put it all together

Everything you’ve learned so far has been building to this point: You can now modify the iot-perishable-network into a more realistic blockchain application. Here’s what you will do in this section:

  • Make changes to the network model:
    • Add new participants to represent the IoT sensors in the shipping container
    • Add new transactions that more realistically model a shipping workflow
    • Add new events that are broadcast when certain points in the workflow are reached
  • Add chaincode for the new transactions
  • Modify the network’s permissions
  • Add Cucumber feature tests to unit test your changes
  • Build the network and run the unit tests
  • Deploy the network to your local Hyperledger Fabric
  • Issue IDs for the Grower, Temperature and GPS sensors, Shipper, and Importer
  • Run transactions through the CLI

When you’re finished modifying the iot-perishable-network, you will have learned the basic skills needed to use Hyperledger Composer, you will be ready to tackle a real-world blockchain application project, you will be the envy of all your friends, and tales of your Hyperledger Composer and Fabric prowess will spread far and wide (okay, I may be embellishing a bit with those last two).

If you get stuck at any point and need a little help, be sure to check out the solution code, which is in a project called iot-perishable-network-advanced that you have already cloned to your computer when you cloned the IBM-Developer project from GitHub.

Note: You are welcome to use whatever editor you’re comfortable with, but I will use VSCode (which you installed in Part 2) for all the changes in this section, and the instructions below will reflect that. If you’re not using VSCode, please adapt accordingly.

5a

Make model changes

To make the changes listed above, you’ll start with the model. Open the models/perishable.cto model file in VSCode and make the changes below. (Note: I have omitted comments from the model snippets below only to save space in this tutorial.) If you look at iot-perishable-network-advanced, you’ll see that I have added comments for each model element I added, and I recommend that you do the same.

First, modify the ShipmentReceived transaction to add an optional property called receivedDateTime, and then add three new transactions that extend ShipmentTransaction to update the blockchain (ledger) when a shipment has been (1) packed, (2) picked up for loading, and (3) loaded onto the container ship:

transaction ShipmentReceived extends ShipmentTransaction {
  o DateTime receivedDateTime optional
}

transaction ShipmentPacked extends ShipmentTransaction {
}

transaction ShipmentPickup extends ShipmentTransaction {
}

transaction ShipmentLoaded extends ShipmentTransaction {
}

Next, modify the Shipment asset to add four new properties for the shipment-related transactions so that when these transactions run, they are stored in the blockchain with the Shipment.

asset Shipment identified by shipmentId {
  o String shipmentId
  o ProductType type
  o ShipmentStatus status
  o Long unitCount
  --> Contract contract
  o TemperatureReading[] temperatureReadings optional
  o GpsReading[] gpsReadings optional
  o ShipmentPacked shipmentPacked optional
  o ShipmentPickup shipmentPickup optional
  o ShipmentLoaded shipmentLoaded optional
  o ShipmentReceived shipmentReceived optional
}

Now add an abstract participant to represent an IoT device that is identified by a String property called deviceId, and add two subclasses of it: one to represent a temperature sensor, and another to represent a GPS sensor. These participants will update the blockchain with their respective readings. Devices are network participants? That’s right; a participant in a blockchain network does not have to be a human being.

abstract participant IoTDevice identified by deviceId {
  o String deviceId
}

participant TemperatureSensor extends IoTDevice {
}

participant GpsSensor extends IoTDevice {
}

Finally, in a real blockchain application, events would be emitted at each important point in the workflow: when a shipment has been packed, when it has been picked up, and so forth. Add four new events to represent these points in the shipment workflow.

event ShipmentPackedEvent {
  o String message
  --> Shipment shipment
}

event ShipmentPickupEvent {
  o String message
  --> Shipment shipment
}

event ShipmentLoadedEvent {
  o String message
  --> Shipment shipment
}

event ShipmentReceivedEvent {
  o String message
  --> Shipment shipment
}

That’s it for the model changes. Next, you need to make a few modifications to the existing JavaScript transactions, and add chaincode for the new transactions.

5b

Add transaction chaincode

Now add the following lines of code to instantiate a TemperatureSensor and GpsSensor as part of the testing setup. Replace the contents of the setupDemo function in lib/logic.js with the listing below:

async function setupDemo(setupDemo) {  // eslint-disable-line no-unused-vars

    const factory = getFactory();
    const NS = 'org.acme.shipping.perishable';

    // create the grower
    const grower = factory.newResource(NS, 'Grower', 'farmer@email.com');
    const growerAddress = factory.newConcept(NS, 'Address');
    growerAddress.country = 'USA';
    grower.address = growerAddress;
    grower.accountBalance = 0;

    // create the importer
    const importer = factory.newResource(NS, 'Importer', 'supermarket@email.com');
    const importerAddress = factory.newConcept(NS, 'Address');
    importerAddress.country = 'UK';
    importer.address = importerAddress;
    importer.accountBalance = 0;

    // create the shipper
    const shipper = factory.newResource(NS, 'Shipper', 'shipper@email.com');
    const shipperAddress = factory.newConcept(NS, 'Address');
    shipperAddress.country = 'Panama';
    shipper.address = shipperAddress;
    shipper.accountBalance = 0;

    // create the contract
    const contract = factory.newResource(NS, 'Contract', 'CON_001');
    contract.grower = factory.newRelationship(NS, 'Grower', 'farmer@email.com');
    contract.importer = factory.newRelationship(NS, 'Importer', 'supermarket@email.com');
    contract.shipper = factory.newRelationship(NS, 'Shipper', 'shipper@email.com');
    const tomorrow = setupDemo.timestamp;
    tomorrow.setDate(tomorrow.getDate() + 1);
    contract.arrivalDateTime = tomorrow; // the shipment has to arrive tomorrow
    contract.unitPrice = 0.5; // pay 50 cents per unit
    contract.minTemperature = 2; // min temperature for the cargo
    contract.maxTemperature = 10; // max temperature for the cargo
    contract.minPenaltyFactor = 0.2; // we reduce the price by 20 cents for every degree below the min temp
    contract.maxPenaltyFactor = 0.1; // we reduce the price by 10 cents for every degree above the max temp

    // create the shipment
    const shipment = factory.newResource(NS, 'Shipment', 'SHIP_001');
    shipment.type = 'BANANAS';
    shipment.status = 'IN_TRANSIT';
    shipment.unitCount = 5000;
    shipment.contract = factory.newRelationship(NS, 'Contract', 'CON_001');

    // create the Temperature sensor
    var temperatureSensor = factory.newResource(NS, 'TemperatureSensor', 'SENSOR_TEMP001');

    // create the GPS sensor
    var gpsSensor = factory.newResource(NS, 'GpsSensor', 'SENSOR_GPS001');

    // add the growers
    const growerRegistry = await getParticipantRegistry(NS + '.Grower');
    await growerRegistry.addAll([grower]);

    // add the importers
    const importerRegistry = await getParticipantRegistry(NS + '.Importer');
    await importerRegistry.addAll([importer]);

    // add the shippers
    const shipperRegistry = await getParticipantRegistry(NS + '.Shipper');
    await shipperRegistry.addAll([shipper]);

    // add the temperature sensor
    const temperatureSensorRegistry = await getParticipantRegistry(NS + '.TemperatureSensor');
    await temperatureSensorRegistry.addAll([temperatureSensor]);

    // add the GPS sensor
    const gpsSensorRegistry = await getParticipantRegistry(NS + '.GpsSensor');
    await gpsSensorRegistry.addAll([gpsSensor]);

    // add the contracts
    const contractRegistry = await getAssetRegistry(NS + '.Contract');
    await contractRegistry.addAll([contract]);

    // add the shipments
    const shipmentRegistry = await getAssetRegistry(NS + '.Shipment');
    await shipmentRegistry.addAll([shipment]);
}

Next, in logic.js, rename the payOut function to receiveShipment so that it more closely matches the transaction in the model. Replace the entirety of the function with the contents below (including the function comment):

/**
 * A shipment has been received by an importer
 * @param {org.acme.shipping.perishable.ShipmentReceived} shipmentReceived - the ShipmentReceived transaction
 * @transaction
 */
async function receiveShipment(shipmentReceived) {  // eslint-disable-line no-unused-vars

    const contract = shipmentReceived.shipment.contract;
    const shipment = shipmentReceived.shipment;
    let payOut = contract.unitPrice * shipment.unitCount;

    console.log('Received at: ' + shipmentReceived.timestamp);
    console.log('Contract arrivalDateTime: ' + contract.arrivalDateTime);

    // set the status of the shipment
    shipment.status = 'ARRIVED';

    // if the shipment did not arrive on time the payout is zero
    if (shipmentReceived.timestamp > contract.arrivalDateTime) {
        payOut = 0;
        console.log('Late shipment');
    } else {
        // find the lowest temperature reading
        if (shipment.temperatureReadings) {
            // sort the temperatureReadings by centigrade
            shipment.temperatureReadings.sort(function (a, b) {
                return (a.centigrade - b.centigrade);
            });
            const lowestReading = shipment.temperatureReadings[0];
            const highestReading = shipment.temperatureReadings[shipment.temperatureReadings.length - 1];
            let penalty = 0;
            console.log('Lowest temp reading: ' + lowestReading.centigrade);
            console.log('Highest temp reading: ' + highestReading.centigrade);

            // does the lowest temperature violate the contract?
            if (lowestReading.centigrade < contract.minTemperature) {
                penalty += (contract.minTemperature - lowestReading.centigrade) * contract.minPenaltyFactor;
                console.log('Min temp penalty: ' + penalty);
            }

            // does the highest temperature violate the contract?
            if (highestReading.centigrade > contract.maxTemperature) {
                penalty += (highestReading.centigrade - contract.maxTemperature) * contract.maxPenaltyFactor;
                console.log('Max temp penalty: ' + penalty);
            }

            // apply any penalities
            payOut -= (penalty * shipment.unitCount);

            if (payOut < 0) {
                payOut = 0;
            }
        }
    }

    console.log('Payout: ' + payOut);
    contract.grower.accountBalance += payOut;
    contract.importer.accountBalance -= payOut;

    console.log('Grower: ' + contract.grower.$identifier + ' new balance: ' + contract.grower.accountBalance);
    console.log('Importer: ' + contract.importer.$identifier + ' new balance: ' + contract.importer.accountBalance);

    var NS = 'org.acme.shipping.perishable';
    // Store the ShipmentReceived transaction with the Shipment asset it belongs to
    shipment.shipmentReceived = shipmentReceived;

    var factory = getFactory();
    var shipmentReceivedEvent = factory.newEvent(NS, 'ShipmentReceivedEvent');
    var message = 'Shipment ' + shipment.$identifier + ' received';
    console.log(message);
    shipmentReceivedEvent.message = message;
    shipmentReceivedEvent.shipment = shipment;
    emit(shipmentReceivedEvent);

    // update the grower's balance
    const growerRegistry = await getParticipantRegistry('org.acme.shipping.perishable.Grower');
    await growerRegistry.update(contract.grower);

    // update the importer's balance
    const importerRegistry = await getParticipantRegistry('org.acme.shipping.perishable.Importer');
    await importerRegistry.update(contract.importer);

    // update the state of the shipment
    const shipmentRegistry = await getAssetRegistry('org.acme.shipping.perishable.Shipment');
    await shipmentRegistry.update(shipment);
}

Now you’ll need to add JavaScript chaincode for the three new transactions you added to the model.

Transaction: ShipmentPacked chaincode

First, add a function to handle the ShipmentPacked transaction. When the Grower participant (or one of its authorized agents) packs the shipment for pickup and transport, it invokes the ShipmentPacked blockchain transaction to record this fact in the ledger.

When this transaction is invoked, a ShipmentPackedEvent is emitted notifying any interested parties of this, and then the chaincode updates the ledger.

Name the new function packShipment using the “verb/object” function naming idiom. In JavaScript code, the comments shown below are mandatory for Hyperledger Composer to recognize the function as a transaction.

Copy all of the code below (including the function comment) and paste it into logic.js:

/**
 * ShipmentPacked transaction - invoked when the Shipment is packed and ready for pickup.
 *
 * @param {org.acme.shipping.perishable.ShipmentPacked} shipmentPacked - the ShipmentPacked transaction
 * @transaction
 */
async function packShipment(shipmentPacked) {  // eslint-disable-line no-unused-vars
    var shipment = shipmentPacked.shipment;
    var NS = 'org.acme.shipping.perishable';
    var factory = getFactory();

    // Add the ShipmentPacked transaction to the ledger (via the Shipment asset)
    shipment.shipmentPacked = shipmentPacked;

    // Create the message
    var message = 'Shipment packed for shipment ' + shipment.$identifier;

    // Log it to the JavaScript console
    //console.log(message);

    // Emit a notification telling subscribed listeners that the shipment has been packed
    var shipmentPackedEvent = factory.newEvent(NS, 'ShipmentPackedEvent');
    shipmentPackedEvent.shipment = shipment;
    shipmentPackedEvent.message = message;
    emit(shipmentPackedEvent);

    // Update the Asset Registry
    const shipmentRegistry = await getAssetRegistry(NS + '.Shipment');
    await shipmentRegistry.update(shipment);
}

Transaction: ShipmentPickup chaincode

Next, when the Shipper participant (or one of its authorized agents) picks up the packed shipment for transport, it invokes the ShipmentPickup blockchain transaction to record this fact in the ledger. Add a function to handle the ShipmentPickup transaction called pickupShipment.

When this transaction is invoked, a ShipmentPickupEvent is emitted, notifying any interested parties of this, and then the chaincode updates the ledger.

Copy all of the code below (including the function comment) and paste it into logic.js:

/**
 * ShipmentPickup - invoked when the Shipment has been picked up from the packer.
 *
 * @param {org.acme.shipping.perishable.ShipmentPickup} shipmentPickup - the ShipmentPickup transaction
 * @transaction
 */
async function pickupShipment(shipmentPickup) {  // eslint-disable-line no-unused-vars
    var shipment = shipmentPickup.shipment;
    var NS = 'org.acme.shipping.perishable';
    var factory = getFactory();

    // Add the ShipmentPacked transaction to the ledger (via the Shipment asset)
    shipment.shipmentPickup = shipmentPickup;

    // Create the message
    var message = 'Shipment picked up for shipment ' + shipment.$identifier;

    // Log it to the JavaScript console
    //console.log(message);

    // Emit a notification telling subscribed listeners that the shipment has been packed
    var shipmentPickupEvent = factory.newEvent(NS, 'ShipmentPickupEvent');
    shipmentPickupEvent.shipment = shipment;
    shipmentPickupEvent.message = message;
    emit(shipmentPickupEvent);

    // Update the Asset Registry
    const shipmentRegistry = await getAssetRegistry(NS + '.Shipment');
    await shipmentRegistry.update(shipment);
}

Transaction: ShipmentLoaded chaincode

Finally, when the Shipper participant (or one of its authorized agents) loads the shipment onto the container ship, it invokes the ShipmentLoaded blockchain transaction to record this fact in the ledger. Add a function to handle the ShipmentLoaded event called loadShipment.

When this transaction is invoked, a ShipmentLoadedEvent is emitted, notifying any interested parties of this, and then the chaincode updates the ledger.

Copy all of the code below (including the function comment) and paste it into shipment.js.

/**
 * ShipmentLoaded - invoked when the Shipment has been loaded onto the container ship.
 *
 * @param {org.acme.shipping.perishable.ShipmentLoaded} shipmentLoaded - the ShipmentLoaded transaction
 * @transaction
 */
async function loadShipment(shipmentLoaded) { // eslint-disable-line no-unused-vars
    var shipment = shipmentLoaded.shipment;
    var NS = 'org.acme.shipping.perishable';
    var factory = getFactory();

    // Add the ShipmentPacked transaction to the ledger (via the Shipment asset)
    shipment.shipmentLoaded = shipmentLoaded;

    // Create the message
    var message = 'Shipment loaded for shipment ' + shipment.$identifier;

    // Log it to the JavaScript console
    //console.log(message);

    // Emit a notification telling subscribed listeners that the shipment has been packed
    var shipmentLoadedEvent = factory.newEvent(NS, 'ShipmentLoadedEvent');
    shipmentLoadedEvent.shipment = shipment;
    shipmentLoadedEvent.message = message;
    emit(shipmentLoadedEvent);

    // Update the Asset Registry
    const shipmentRegistry = await getAssetRegistry(NS + '.Shipment');
    await shipmentRegistry.update(shipment);
}
5c

Change access control permissions

In a real blockchain application, it is not ideal to grant any participant permission to do anything they want to. For example, a Grower shouldn’t be able to invoke transactions that make sense only for a Shipper, and a GPS sensor shouldn’t be allowed to record temperature readings in the blockchain.

So how do you prevent that from happening? In other words, how do you build your network such that you control access to its resources? Through the Access Control List (ACL) file.

You’ve already seen it in Parts 1 and 2 of this series, but I just sort of glossed over it, and promised to cover it in more detail at a later point. Well, that time has come.

It’s time to talk about access control, which begins with permissions.acl. You saw this file earlier, but it’s time to put that knowledge to use. You’ll modify the iot-perishable-network to ensure that resources can be accessed only by certain participants that make sense to access that resource. As an illustration, I’ve summarized the access control settings that make sense for the iot-perishable-network in Table 1.

You may notice the rule names and think they look a bit odd. I like to encode as much information in a name as possible without making it ridiculously long, or completely nonsensical. Here’s my thinking: The rule is named Participant_Operation_Resource, so Grower_R_Grower means “Grant the Grower participant READ access to Grower instances.”

In Table 1, the type of access is provided in parentheses: R = READ, U = UPDATE, C = CREATE.

Table 1. Participant access control

Participant Participants accessed Assets accessed Transactions accessed
Grower Grower (R) Shipment (RU), Contract (RU) ShipmentPacked (C)
Shipper Shipper (R) Shipment (RU), Contract (RU) ShipmentPickup, ShipmentLoaded (C)
Importer Importer (R), Grower (RU) Shipment (RU), Contract (RU) ShipmentReceived (C)
TemperatureSensor NONE Shipment (RU), Contract (RU) TemperatureReading (C)
GpsSensor NONE Shipment (RU), Contract (RU) GpsReading (C)

The rules are codified below. Open permissions.acl (in the root of the network project), delete its current contents, and replace them with the listings that follow. The entire listing is very long, so I’ll take you through it one section at a time.

The first two rules grant permission to the Hyperledger Composer system Participant and NetworkAdmin participants to access everything in the org.hyperledger.composer.system namespace, and every class in the network, respectively.

Copy all of the code below and paste it into permissions.acl.

/**
 * System and Network Admin access rules
 */
rule SystemACL {
  description:  "System ACL to permit all access"
  participant: "org.hyperledger.composer.system.Participant"
  operation: ALL
  resource: "org.hyperledger.composer.system.**"
  action: ALLOW
}

rule NetworkAdminUser {
    description: "Grant business network administrators full access to user resources"
    participant: "org.hyperledger.composer.system.NetworkAdmin"
    operation: ALL
    resource: "**"
    action: ALLOW
}

The next set of rules grants permission for the participants in the iot-perishable-network to access other participants (including themselves). This access varies as it makes sense for the business functions of the network. For example, the Grower can only access the Grower class, but since the Importer executes the ShipmentReceived transaction (which updates the Importer’s and Grower’s account balances), the Importer needs READ and UPDATE access to both Importer and Grower.

Copy all of the code below and paste it into permissions.acl.

/**
 * Rules for Participant registry access
 */
rule Grower_R_Grower {
    description: "Grant Growers access to Grower resources"
    participant: "org.acme.shipping.perishable.Grower"
    operation: READ
    resource: "org.acme.shipping.perishable.Grower"
    action: ALLOW
}

rule Shipper_R_Shipper {
    description: "Grant Shippers access to Shipper resources"
    participant: "org.acme.shipping.perishable.Shipper"
    operation: READ
    resource: "org.acme.shipping.perishable.Shipper"
    action: ALLOW
}

rule Importer_RU_Importer {
    description: "Grant Importers access to Importer resources"
    participant: "org.acme.shipping.perishable.Importer"
    operation: READ,UPDATE
    resource: "org.acme.shipping.perishable.Importer"
    action: ALLOW
}

rule Importer_RU_Grower {
    description: "Grant Importers access to Grower participant"
    participant: "org.acme.shipping.perishable.Importer"
    operation: READ,UPDATE
    resource: "org.acme.shipping.perishable.Grower"
    action: ALLOW
}

Next are the rules for access to the network’s assets. In this case, I want to grant access to the Shipment and Contract resources to all participants.

Copy all of the code below and paste it into permissions.acl:

/**
 * Rules for Asset registry access
 */
rule ALL_RU_Shipment {
    description: "Grant All Participants in org.acme.shipping.perishable namespace READ/UPDATE access to Shipment assets"
    participant: "org.acme.shipping.perishable.*"
    operation: READ,UPDATE
    resource: "org.acme.shipping.perishable.Shipment"
    action: ALLOW
}

rule ALL_RU_Contract {
    description: "Grant All Participants in org.acme.shipping.perishable namespace READ/UPDATE access to Contract assets"
    participant: "org.acme.shipping.perishable.*"
    operation: READ,UPDATE
    resource: "org.acme.shipping.perishable.Contract"
    action: ALLOW
}

Next are the rules for invoking transactions (which require CREATE access). As you can see, these are on a case-by-case basis as it makes sense. For example, the Grower participant does not need to access the Shipper transactions (and vice versa).

Copy all of the code below and paste it into permissions.acl.

/**
 * Rules for Transaction invocations
 */
rule Grower_C_ShipmentPacked {
    description: "Grant Growers access to invoke ShipmentPacked transaction"
    participant: "org.acme.shipping.perishable.Grower"
    operation: CREATE
    resource: "org.acme.shipping.perishable.ShipmentPacked"
    action: ALLOW
}

rule Shipper_C_ShipmentPickup {
    description: "Grant Shippers access to invoke ShipmentPickup transaction"
    participant: "org.acme.shipping.perishable.Shipper"
    operation: CREATE
    resource: "org.acme.shipping.perishable.ShipmentPickup"
    action: ALLOW
}

rule Shipper_C_ShipmentLoaded {
    description: "Grant Shippers access to invoke ShipmentLoaded transaction"
    participant: "org.acme.shipping.perishable.Shipper"
    operation: CREATE
    resource: "org.acme.shipping.perishable.ShipmentLoaded"
    action: ALLOW
}

rule GpsSensor_C_GpsReading {
    description: "Grant IoT GPS Sensor devices full access to the appropriate transactions"
    participant: "org.acme.shipping.perishable.GpsSensor"
    operation: CREATE
    resource: "org.acme.shipping.perishable.GpsReading"
    action: ALLOW
}

rule TemperatureSensor_C_TemperatureReading {
    description: "Grant IoT Temperature Sensor devices full access to the appropriate transactions"
    participant: "org.acme.shipping.perishable.TemperatureSensor"
    operation: CREATE
    resource: "org.acme.shipping.perishable.TemperatureReading"
    action: ALLOW
}

rule Importer_C_ShipmentReceived {
    description: "Grant Importers access to invoke the ShipmentReceived transaction"
    participant: "org.acme.shipping.perishable.Importer"
    operation: CREATE
    resource: "org.acme.shipping.perishable.ShipmentReceived"
    action: ALLOW
}

The last rule says (in effect), “If there is some resource that we did not explicitly grant access to above, deny access to it.” This is currently Hyperledger Composer’s default behavior, but if that changes in a subsequent release, this rule makes sure the network behaves like this regardless of the default behavior.

Copy all of the code below and paste it into permissions.acl:

/**
 * Make sure all resources are locked down by default.
 * If permissions need to be granted to certain resources, that should happen
 * above this rule. Anything not explicitly specified gets locked down.
 */
rule Default {
    description: "Deny all participants access to all resources"
    participant: "ANY"
    operation: ALL
    resource: "org.acme.shipping.perishable.*"
    action: DENY
}

That’s all for permissions.acl. Save the file, and get ready to unit test these permissions.

5d

Add feature tests

Wait, unit test permissions? That’s right. All of the permissions in permissions.acl can be unit tested using Cucumber. I introduced you to Cucumber in Part 2, and now you’ll use Cucumber to unit test the new transaction logic and the ACL rules you added to permissions.acl in the previous section.

The cool thing about Cucumber (pun intended) is that its syntax (Gherkin) is intuitive, so I don’t need to explain a lot of the tests you’re about to see.

Basic test scenarios

In VSCode, open iot-perishable.feature, then delete its contents and replace them with the listings below as you work through this section.

First, you will set up the feature test as shown below. Notice on lines 14-16 that the test issues identities (that is, ID cards) for the specified participants. These identities will be used later in the unit test to test the security permissions you added to permissions.acl in the previous section.

Copy all of the code below and paste it into iot-perishable.feature.

Feature: Basic Test Scenarios
    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}
        ]
        """
        And I have added the following participants of type org.acme.shipping.perishable.TemperatureSensor
            | deviceId |
            | TEMP_001 |
        And I have issued the participant org.acme.shipping.perishable.Grower#grower@email.com with the identity grower1
        And I have issued the participant org.acme.shipping.perishable.Importer#supermarket@email.com with the identity importer1
        And I have issued the participant org.acme.shipping.perishable.TemperatureSensor#TEMP_001 with the identity sensor_temp1
        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  |
        And 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 use the identity importer1

I recommend that you always put a “default identity” in your Cucumber tests to ensure that your security permissions are always being tested. You can always override this with a subsequent “When I use the identity xyz” in a specific scenario test if you like, as you’ll see in this section.

The first scenario you’ll run is when there are no temperature readings outside the agreed-upon range. Notice on line 2 that this test will be run as the identity importer1, which is an Importer.

Copy all of the code below and paste it into iot-perishable.feature:

Scenario: When the temperature range is within the agreed-upon boundaries
    When I use the identity importer1
    And 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}
    ]
    """

The next scenario tests the case when there is a temperature reading that is below the agreed-upon threshold. The payout amount should be less (because of the low temperature penalty in the contract).

Notice that the test overrides the default identity, and is run first as sensor_temp1, which is a TemperatureSensor participant (line 2), then the current identity is switched to importer1, which is an Importer (line 6). If the test is not run this way, it will attempt to execute the TemperatureReading transaction as importer1 (which was set in the Background section), which does not have permission to invoke that transaction. Then the identity must be switched back or the ShipmentReceived transaction call will fail because a TemperatureSensor participant does not have permission to invoke that transaction.

Copy all of the code below and paste it into iot-perishable.feature:

Scenario: When the low/min temperature threshold is breached by 2 degrees C
    When I use the identity sensor_temp1
    And I submit the following transaction of type org.acme.shipping.perishable.TemperatureReading
        | shipment | centigrade |
        | SHIP_001 | 0          |
    Then I use the identity importer1
    And 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}
    ]
    """

The next scenario tests the case when there is a temperature reading that is above the threshold. Like the previous scenario, this one must also be run using two separate identities.

Copy all of the code below and paste it into iot-perishable.feature:

Scenario: When the hi/max temperature threshold is breached by 2 degrees C
    When I use the identity sensor_temp1
    And I submit the following transaction of type org.acme.shipping.perishable.TemperatureReading
        | shipment | centigrade |
        | SHIP_001 | 12          |
    Then I use the identity importer1
    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}
    ]
    """

Finally, the ShipmentReceived transaction must be run as an Importer (the only participant that has permission to invoke it), and when it does an event is emitted. The identity is set for this scenario in the Background section.

Copy all of the code below and paste it into iot-perishable.feature:

Scenario: When shipment is received a ShipmentReceivedEvent should be broadcast
    When I submit the following transaction of type org.acme.shipping.perishable.ShipmentReceived
        | shipment |
        | SHIP_001 |
    Then I should have received the following event of type org.acme.shipping.perishable.ShipmentReceivedEvent
        | message                    | shipment |
        | Shipment SHIP_001 received | SHIP_001 |

That’s all for iot-perishable.feature. Go ahead and save the file.

Tests for IoT devices

The remaining feature tests work essentially the same as the ones you just saw, so I won’t bore you with a repeated explanation. As you complete this section, just follow the directions, and paste the listings as a whole into the new .feature files.

In VSCode, click on the features directory and choose New File, and call the file sensors.feature. When the file opens in the editor window, copy and paste the following listing into the new empty file.

Copy all of the code below and paste it into sensors.feature.

Feature: Tests related to IoT Devices

    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.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Panama"}, "accountBalance":0},
        {"$class":"org.acme.shipping.perishable.Importer", "email":"importer@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"UK"}, "accountBalance":0}
        ]
        """
        And I have added the following participants of type org.acme.shipping.perishable.TemperatureSensor
            | deviceId |
            | TEMP_001 |
        And I have added the following participant of type org.acme.shipping.perishable.GpsSensor
            | deviceId |
            | GPS_001  |
        And I have issued the participant org.acme.shipping.perishable.Grower#grower@email.com with the identity grower1
        And I have issued the participant org.acme.shipping.perishable.Shipper#shipper@email.com with the identity shipper1
        And I have issued the participant org.acme.shipping.perishable.TemperatureSensor#TEMP_001 with the identity sensor_temp1
        And I have issued the participant org.acme.shipping.perishable.GpsSensor#GPS_001 with the identity sensor_gps1
        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 | shipper@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  |

    Scenario: Test TemperatureThresholdEvent is emitted when the max temperature threshold is violated
        When I use the identity sensor_temp1
        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 TemperatureThresholdEvent is emitted when the min temperature threshold is violated
        When I use the identity sensor_temp1
        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 ShipmentInPortEvent is emitted when GpsReading indicates arrival at destination port
        When I use the identity sensor_gps1
        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 |

    Scenario: GpsSensor sensor_gps1 can invoke GpsReading transaction
        When I use the identity sensor_gps1
        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 |

    Scenario: Temperature Sensor cannot invoke GpsReading transaction
        When I use the identity sensor_temp1
        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 get an error matching /Participant .* does not have 'CREATE' access to resource/

    Scenario: Gps Sensor cannot invoke TemperatureReading transaction
        When I use the identity sensor_gps1
        When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
            | shipment | centigrade |
            | SHIP_001 | 11         |
        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/

    Scenario: Grower cannot invoke TemperatureReading transaction
        When I use the identity sensor_gps1
        When I submit the following transactions of type org.acme.shipping.perishable.TemperatureReading
            | shipment | centigrade |
            | SHIP_001 | 11         |
        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/

Now save this file.

In VSCode, click the features directory, choose New File, and call the file grower.feature. When the file opens in the editor window, copy and paste the following listing into the new empty file.

Copy all of the code below and paste it into grower.feature.

Feature: Tests related to Growers

    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.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Paname"}, "accountBalance":0}
        ]
        """
        And I have issued the participant org.acme.shipping.perishable.Grower#grower@email.com with the identity grower1
        And I have issued the participant org.acme.shipping.perishable.Shipper#shipper@email.com with the identity shipper1
        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 | shipper@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 use the identity grower1

    Scenario: grower1 can read Grower assets
        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":0}
        ]
        """

    Scenario: grower1 invokes the ShipmentPacked transaction
        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentPacked
            | shipment |
            | SHIP_001 |
        Then I should have received the following event of type org.acme.shipping.perishable.ShipmentPackedEvent
            | message                               | shipment |
            | Shipment packed for shipment SHIP_001 | SHIP_001 |

    Scenario: shipper1 cannot read Grower assets
        When I use the identity shipper1
        And 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":0}
        ]
        """
        Then I should get an error matching /Object with ID .* does not exist/

    Scenario: shipper1 cannot invoke the ShipmentPacked transaction
        When I use the identity shipper1
        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentPacked
            | shipment |
            | SHIP_001 |
        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/

Now save this file. Notice the default identity in use. You need to set an identity for a specific scenario only if it needs a different identity.

In VSCode, click the features directory, choose New File, and call the file shipper.feature. When the file opens in the editor window, copy and paste the following listing into the new empty file.

Copy all of the code below and paste it into shipper.feature.

Feature: Tests related to Shippers

    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.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Paname"}, "accountBalance":0}
        ]
        """
        And I have issued the participant org.acme.shipping.perishable.Grower#grower@email.com with the identity grower1
        And I have issued the participant org.acme.shipping.perishable.Shipper#shipper@email.com with the identity shipper1
        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 | shipper@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 use the identity shipper1

    Scenario: shipper1 can read Shipper assets
        Then I should have the following participants
        """
        [
        {"$class":"org.acme.shipping.perishable.Shipper", "email":"shipper@email.com", "address":{"$class":"org.acme.shipping.perishable.Address", "country":"Paname"}, "accountBalance":0}
        ]
        """

    Scenario: shipper1 invokes the ShipmentPickup transaction
        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentPickup
            | shipment |
            | SHIP_001 |
        Then I should have received the following event of type org.acme.shipping.perishable.ShipmentPickupEvent
            | message                                  | shipment |
            | Shipment picked up for shipment SHIP_001 | SHIP_001 |

    Scenario: shipper1 invokes the ShipmentLoaded transaction
        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentLoaded
            | shipment |
            | SHIP_001 |
        Then I should have received the following event of type org.acme.shipping.perishable.ShipmentLoadedEvent
            | message                               | shipment |
            | Shipment loaded for shipment SHIP_001 | SHIP_001 |

    Scenario: grower1 cannot invoke the ShipmentPickup transaction
        When I use the identity grower1
        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentPickup
            | shipment |
            | SHIP_001 |
        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/

    Scenario: grower1 cannot invoke the ShipmentPickup transaction
        When I use the identity grower1
        And I submit the following transaction of type org.acme.shipping.perishable.ShipmentLoaded
            | shipment |
            | SHIP_001 |
        Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/

Now save this file.

That’s it for the Cucumber feature tests.

5e

Build and unit test the network

Now it’s time to build and unit test your network. Go to the command line (Ubuntu) or open a terminal window (MacOS) and enter this command:

npm install && npm test

You have run these commands before in Part 2 of this tutorial series. You’ll see a lot of output, but when the unit tests have run, you should see this:

   And I have issued the participant org.acme.shipping.perishable.Shipper#shipper@email.com with the identity shipper1
   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 | shipper@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 use the identity shipper1
   When I use the identity grower1
   And I submit the following transaction of type org.acme.shipping.perishable.ShipmentLoaded
      | shipment |
      | SHIP_001 |
   Then I should get an error matching /Participant .* does not have 'CREATE' access to resource/

20 scenarios (20 passed)
229 steps (229 passed)
0m09.497s

Lines 15-16 above show that the 20 scenarios (consisting of 229 steps) have all run without error. You’re now ready to deploy the network!

6

Deploy your changes to the network

If you’ve been following along with this tutorial, you have already deployed the iot-perishable-network to the local Hyperledger Fabric and instantiated the model. Unfortunately, you need to add TemperatureSensor and GpsSensor participants to the participant registry. You can do this through the CLI, but I want to show you how to tear down and clean up your local Hyperledger Fabric, which is something you need to do from time to time during development.

When you tear down the Hyperledger Fabric using the procedure outlined below, it removes the network and any crypto materials you’ve generated so far. Now, you never want to do this in production, for obvious reasons, but this is development, so it’s something you’re going to need to know how to do.

Navigate to your $COMPOSER_ROOT directory and enter these commands:

$ ./teardownFabric.s
$ rm -Rf ~/.composer/
$ ./teardownAllDocker.sh
$ ./startFabric.sh
$ ./createPeerAdminCard.sh

Note: For the teardownAllDocker.sh command, you’ll see a list of choices. Choose option 2 to remove all Docker images.

These commands (respectively):

  • Stop the Fabric and the Docker images from running
  • Remove all crypto materials
  • Remove all Docker images (make sure to select option 2)
  • Start the Fabric (downloads new copies of the images)
  • Create the PeerAdmin card

Next, navigate to the iot-perishable-network directory, deploy the network, and import the admin@iot-perishable-network card again:

$ composer network install --card PeerAdmin@hlfv1 --archiveFile dist/iot-perishable-network.bna
 Installing business network. This may take a minute...
Successfully installed business network iot-perishable-network, version 0.2.6
Command succeeded
$ composer network start --networkName iot-perishable-network --networkVersion 0.2.6 --networkAdmin admin --networkAdminEnrollSecret adminpw --card PeerAdmin@hlfv1 --file networkadmin.card
Starting business network iot-perishable-network at version 0.2.6
Processing these Network Admins:
        userName: admin
 Starting business network definition. This may take a minute...
Successfully created business network card:
        Filename: networkadmin.card
Command succeeded
$ composer card import --file networkadmin.card
Successfully imported business network card
        Card file: networkadmin.card
        Card name: admin@iot-perishable-network
Command succeeded

Finally, you need to execute the SetupDemo transaction to instantiate the model, or there will be no participants for which to issue ID cards!

$ composer transaction submit --card admin@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.SetupDemo"}'
Transaction Submitted.

Command succeeded
6a

Issue IDs

Now it’s time to issue some IDs. You did this in Playground earlier. Now you will use the CLI to issue ID cards for the participants shown in Table 2.

Table 2. Participant Identities to be issued

Participant Identity ID card file name
Grower grower1 grower1.card
Shipper shipper1 shipper1.card
Importer importer1 importer1.card
TemperatureSensor sensor_temp1 sensor_temp1.card
GpsSensor sensor_gps1 sensor_gps1.card

In the CLI section, you invoked the SetupDemo transaction, which instantiated the network. Remember, you cannot issue an ID for a participant that is not in the Participant Registry.

To issue an ID card for a participant, first execute the composer identity issue command, specifying the card file, and then import the card file into the local wallet. Use the admin@iot-perishable-network card to authenticate when you execute the composer identity issue command.

The general format for the command for the iot-perishable-network is this:

composer identity issue --card admin@iot-perishable-network --file ID_CARD_FILE --newUserId IDENTITY --participantId 'resource:org.acme.shipping.perishable.PARTICIPANT#PARTICIPANT_ID'

Where:

ID_CARD_FILE — is the file name where the ID card will be stored (see Table 2).

IDENTITY — is the identity that is to be issued (see Table 2).

PARTICIPANT_CLASS — is the participant class (for example, Grower).

PARTICIPANT_ID — is the ID of the participant when it was instantiated in the registry (for example, farmer@email.com).

ID Card: Grower

$ composer identity issue --card admin@iot-perishable-network --file grower1.card --newUserId grower1 --participantId 'resource:org.acme.shipping.perishable.Grower#farmer@email.com'
Issue identity and create Network Card for: grower1
 Issuing identity. This may take a few seconds...
Successfully created business network card file to
        Output file: grower1.card
Command succeeded
$ composer card import --file grower1.card
Successfully imported business network card
        Card file: grower1.card
        Card name: grower1@iot-perishable-network
Command succeeded

ID Card: Shipper

$ composer identity issue --card admin@iot-perishable-network --file shipper1.card --newUserId shipper1 --participantId 'resource:org.acme.shipping.perishable.Shipper#shipper@email.com'
Issue identity and create Network Card for: shipper1
 Issuing identity. This may take a few seconds...
Successfully created business network card file to
        Output file: shipper1.card
Command succeeded
$ composer card import --file shipper1.card
Successfully imported business network card
        Card file: shipper1.card
        Card name: shipper1@iot-perishable-network
Command succeeded

ID Card: Importer

$ composer identity issue --card admin@iot-perishable-network --file importer1.card --newUserId importer1 --participantId 'resource:org.acme.shipping.perishable.Importer#supermarket@email.com'
\Issue identity and create Network Card for: importer1
 Issuing identity. This may take a few seconds...
Successfully created business network card file to 
        Output file: importer1.card
Command succeeded
$ composer card import --file importer1.card
Successfully imported business network card
        Card file: importer1.card
        Card name: importer1@iot-perishable-network
Command succeeded

ID Card: TemperatureSensor

$ composer identity issue --card admin@iot-perishable-network --file sensor_temp1.card --newUserId sensor_temp1 --participantId 'resource:org.acme.shipping.perishable.TemperatureSensor#SENSOR_TEMP001'
Issue identity and create Network Card for: sensor_temp1
 Issuing identity. This may take a few seconds...
Successfully created business network card file to
        Output file: sensor_temp1.card
Command succeeded
$ composer card import --file sensor_temp1.card
Successfully imported business network card
        Card file: sensor_temp1.card
        Card name: sensor_temp1@iot-perishable-network
Command succeeded

ID Card: GpsSensor

$ composer identity issue --card admin@iot-perishable-network --file sensor_gps1.card --newUserId sensor_gps1 --participantId 'resource:org.acme.shipping.perishable.GpsSensor#SENSOR_GPS001'
Issue identity and create Network Card for: sensor_gps1
 Issuing identity. This may take a few seconds...
Successfully created business network card file to
        Output file: sensor_gps1.card
Command succeeded
$ composer card import --file sensor_gps1.card
Successfully imported business network card
        Card file: sensor_gps1.card
        Card name: sensor_gps1@iot-perishable-network
Command succeeded

Now that you’ve issued ID cards for all the participants in the network and imported those cards into your local Hyperledger Fabric wallet, you can simulate the workflow of the IoT Perishable Goods business network from the command line using the CLI.

To see the cards you’ve issued, run the composer card list command:

$ composer card list
The following Business Network Cards are available:
Connection Profile: defaultProfile
┌───────────────┬──────────────┬────────────────────────┐
│ Card Name     │ UserId       │ Business Network       │
├───────────────┼──────────────┼────────────────────────┤
│ grower1       │ grower1      │ iot-perishable-network │
├───────────────┼──────────────┼────────────────────────┤
│ PeerAdminCard │ admin        │                        │
├───────────────┼──────────────┼────────────────────────┤
│ importer1     │ importer1    │ iot-perishable-network │
├───────────────┼──────────────┼────────────────────────┤
│ networkAdmin  │ admin        │ iot-perishable-network │
├───────────────┼──────────────┼────────────────────────┤
│ sensor_gps1   │ sensor_gps1  │ iot-perishable-network │
├───────────────┼──────────────┼────────────────────────┤
│ sensor_temp1  │ sensor_temp1 │ iot-perishable-network │
├───────────────┼──────────────┼────────────────────────┤
│ shipper1      │ shipper1     │ iot-perishable-network │
└───────────────┴──────────────┴────────────────────────┘
Connection Profile: hlfv1
┌─────────────────────────────────────┬──────────────┬────────────────────────┐
│ Card Name                           │ UserId       │ Business Network       │
├─────────────────────────────────────┼──────────────┼────────────────────────┤
│ admin@iot-perishable-network        │ admin        │ iot-perishable-network │
├─────────────────────────────────────┼──────────────┼────────────────────────┤
│ grower1@iot-perishable-network      │ grower1      │ iot-perishable-network │
├─────────────────────────────────────┼──────────────┼────────────────────────┤
│ importer1@iot-perishable-network    │ importer1    │ iot-perishable-network │
├─────────────────────────────────────┼──────────────┼────────────────────────┤
│ sensor_gps1@iot-perishable-network  │ sensor_gps1  │ iot-perishable-network │
├─────────────────────────────────────┼──────────────┼────────────────────────┤
│ sensor_temp1@iot-perishable-network │ sensor_temp1 │ iot-perishable-network │
├─────────────────────────────────────┼──────────────┼────────────────────────┤
│ shipper1@iot-perishable-network     │ shipper1     │ iot-perishable-network │
├─────────────────────────────────────┼──────────────┼────────────────────────┤
│ PeerAdmin@hlfv1                     │ PeerAdmin    │                        │
└─────────────────────────────────────┴──────────────┴────────────────────────┘
Issue composer card list --card <Card Name> to get details a specific card
Command succeeded
6b

Submit transactions

The general format for the composer transaction submit command is:

composer transaction submit --card CARD_NAME -d 'DATA'

Where:

CARD_NAME — is the name of the card to use (see Table 2).

DATA is a JSON object containing the transaction data. You can copy the data format for any transaction from Playground, remove the newlines, and replace the specific data values. (Yes, that’s how I came up with the data for the transactions you’ll see in this section.)

Submit transactions

In this section you’ll submit the following transactions through the CLI:

  • ShipmentPacked
  • ShipmentPickup
  • ShipmentLoaded
  • TemperatureReading
  • GpsReading
  • ShipmentReceived

Each of these transactions corresponds to a step in the workflow of moving perishable goods from the Grower to the Importer.

Submit a ShipmentPacked transaction using the grower1 ID card to authenticate and authorize with the network:

$ composer transaction submit --card grower1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.ShipmentPacked", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded

This transaction succeeded because the Grower participant has permission to execute the ShipmentPacked transaction. Just for fun, try to execute the transaction using the shipper1 ID card:

$ composer transaction submit --card shipper1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.ShipmentPacked", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Error: Error trying invoke business network with transaction id 410a79c15e8c87a0ecfed54deab2245ea46e69bb3335cd93b8f99bc0f29dd9f6. Error: No valid responses from any peers.
Response from attempted peer comms was an error: Error: transaction returned with failure: AccessException: Participant 'org.acme.shipping.perishable.Shipper#shipper@email.com' does not have 'CREATE' access to resource 'org.acme.shipping.perishable.ShipmentPacked#410a79c15e8c87a0ecfed54deab2245ea46e69bb3335cd93b8f99bc0f29dd9f6'
Command failed

The Shipper participant does not have access to the ShipmentPacked transaction, so the attempt fails, showing the permissions you coded earlier in the tutorial are working just as expected (of course, you knew that when you ran the unit test, but it’s always nice to see it in action).

The second step in the workflow of getting a shipment of perishable goods from the Grower to the Importer is to pick up the packed shipment, which is handled by the Shipper:

$ composer transaction submit --card shipper1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.ShipmentPickup", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded

Once the shipment has been picked up, the Shipper loads it onto the container ship and executes the ShipmentLoaded transaction to record this in the ledger:

$ composer transaction submit --card shipper1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.ShipmentLoaded", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded

Along the way, the TemperatureSensor participant is taking readings inside the cargo container and recording them in the ledger. Simulate a few of those like this:

$ composer transaction submit --card sensor_temp1@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 2, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded
$ composer transaction submit --card sensor_temp1@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 3, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded

Ix:~/HyperledgerComposer/developerWorks/iot-perishable-network sperry$ composer transaction submit --card sensor_temp1@iot-perishable-network -d '{ "$class": "org.acme.shipping.perishable.TemperatureReading", "centigrade": 11, "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded

That was three TemperatureReading transactions, with the last one at 11C, which is 1 degree above the contract threshold, so the high temperature penalty will be in effect when the shipment is received.

Now submit a GPS transaction indicating the container ship has reached its destination, which is the Port of New York/New Jersey:

$ composer transaction submit --card sensor_gps1@iot-perishable-network -d '{"$class": "org.acme.shipping.perishable.GpsReading", "readingTime": "2200", "readingDate": "20171118", "latitude": "40.6840", "latitudeDir": "N", "longitude": "74.0062", "longitudeDir": "W", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded

The final step in the workflow is for the Importer to receive the shipment and invoke the ShipmentReceived transaction to record this in the blockchain:

$ composer transaction submit --card importer1@iot-perishable-network -d '{"$class":
"org.acme.shipping.perishable.ShipmentReceived", "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001"}'
Transaction Submitted.

Command succeeded

That doesn’t seem very exciting, does it? Go ahead and fire up the REST server:

$ composer-rest-server
? Enter the name of the business network card to use: admin@iot-perishable-network
? Specify if you want namespaces in the generated REST API: always use namespaces
? Specify if you want to use an API key to secure the REST API: No
? Specify if you want to enable authentication for the REST API using Passport: No
? Specify if you want to enable the explorer test interface: Yes
? Specify a key if you want to enable dynamic logging:
? Specify if you want to enable event publication over WebSockets: No
? Specify if you want to enable TLS security for the REST API: No
To restart the REST server using the same options, issue the following command:
   composer-rest-server -c admin@iot-perishable-network -n always -u true
Discovering types from business network definition ...
Discovering the Returning Transactions..
Discovered types from business network definition
Generating schemas for all types in business network definition ...
Generated schemas for all types in business network definition
Adding schemas for all types to Loopback ...
Added schemas for all types to Loopback
Web server listening at: http://localhost:3000
Browse your REST API at http://localhost:3000/explorer

Now point your browser to localhost:3000, locate the Shipment asset, and execute the /Get method. You will see all of the transactions you submitted, recorded in the blockchain as part of the Shipment asset. Figure 5 shows what you should see in the response body of the /Get request.

Figure 5. Shipment asset showing transactions

Shipment asset showing transactions

There’s quite a bit of data, so I could only show a little of it in Figure 5, but go ahead and fire up the REST server, then scroll through the Shipment asset and see the results for yourself.

Video: Wrap-up demo

All the stuff from the previous section, plus Playground (where applicable), is wrapped up in this video:

Transcript

Conclusion to Part 3

This tutorial covered a lot of ground. First, you installed, started, and then deployed the iot-perishable-network to a local instance of Hyperledger Fabric.

Then you installed and ran the REST interface, which you can use to access the network. I hope you had a chance to watch the video, where I walk through that in more detail. If not, make sure and check that out.

After that, you saw how access control works in Hyperledger Composer, including ACL rules and where they live in the network’s source code.

Then you worked with the Composer Command Line Interface (CLI) to ping the running network, execute the SetupDemo and other transactions, and update the network as you make changes in development.

Finally, the coup de grace: You modified the iot-perishable-network to transform it into a more real-world blockchain application, write Cucumber feature tests, issue IDs for all participants, and execute every transaction through the CLI.

At this point, you should have everything you need to start developing your own blockchain applications using Hyperledger Composer. Good luck!

This content was originally published on IBM developerWorks on December 14, 2017.