Hyperledger Composer

Created: Introduction to Hyperledger Composer

Updated: 03 September 2023

Overview

A business Network, defined in a Business Network Archive .bna File

  • Models
  • Logic
  • Queries
  • Access Control

In order to update a network, we simply upload the Archive .bna file to the network, this can either be done from the command line or from the Blockchain UI if using IBM Cloud

Models

Models are files that define Assets, Participants and Transactions using the Hyperledger Composer Modelling Language

Models are defined in .cto files, and are written using the Hyperledger Composer Modelling Language

CTO Language

CTO Language

A .cto file is composed of the following elements

  1. A single namespace, all resource definitions are part of this namespace
  2. A set of resource definitions
    • Assets
    • Transactions
    • Participants
    • Events
  3. Optional import declarations than import resources from other namespaces

Namespaces

Resources are organized by namespaces, this is defined by first line in a .cto file which can be as follows

1
namespace org.example.mynetwork

All other resources defined in the same file will be part of this namespace

Classes

A resource definition has the following properties

  1. A namespace (defined by the namespace of its parent file)
1
namespace org.example.mynetwork
  1. A name and identifying field
1
/**
2
* A vehicle asset.
3
*/
4
asset Vehicle identified by vin {
5
o String vin
6
}
  1. Optional supertype
  2. Any additional properties
1
/**
2
* A car asset. A car is related to a list of parts
3
*/
4
asset Car extends Vehicle {
5
o String model
6
--> Part[] Parts
7
}
  1. An optional Abstract declaration to state that the type cannot be created but can only be extended
1
/**
2
* An abstract Vehicle asset.
3
*/
4
abstract asset Vehicle identified by vin {
5
o String vin
6
}
  1. A set of relationships to other Composer types
1
/**
2
* A Field asset. A Field is related to a list of animals
3
*/
4
asset Field identified by fieldId {
5
o String fieldId
6
o String name
7
--> Animal[] animals
8
}

Enums

Enumerables can be defined as follows

1
/**
2
* An enumerated type
3
*/
4
enum ProductType {
5
o DAIRY
6
o BEEF
7
o VEGETABLES
8
}

And can be used in another class as a type

1
participant Farmer identified by farmerId {
2
o String farmerId
3
o ProductType primaryProduct
4
}

Concepts

Concepts are abstract classes that are not assets, participants or transactions. They are contained by another resource and do not have an identifier and cannot be directly stored in registries or referenced in relationships

For example we can define a concept as follows

1
abstract concept Address {
2
o String street
3
o String city default ="Winchester"
4
o String country default = "UK"
5
o Integer[] counts optional
6
}
7
8
concept UnitedStatesAddress extends Address {
9
o String zipcode
10
}

And then use it in a class definition

1
participant Farmer identified by farmerId {
2
o String farmerId
3
o UnitedStatesAddress address
4
o ProductType primaryProduct
5
}

Primitive Types

Composer has a few primitive types, namely

  • String
  • Double
  • Integer
  • Long
  • DateTime
  • Boolean

Arrays

Arrays can simply be defined with []

1
o Integer[] integerArray
1
--> Animal[] myAnimals

Relationships

A relationship is a tuple composed of

  1. Namespace being referenced
  2. Type being referenced
  3. Identifier instance being referenced

For example

1
org.example.Vehicle#23451

Field Validation

Attributes may include a default value, string fields may include a regex validation, numerical values may include a range, these can be seen below

1
asset Vehicle extends Base {
2
// An asset contains Fields, each of which can have an optional default value
3
o String reg default="ABC123"
4
// A numeric field can have a range validation expression
5
o Integer year default=2016 range=[1990,] optional // model year must be 1990 or higher
6
o Integer[] integerArray
7
o String V5cID regex=/^[A-z][A-z][0-9]{7}/
8
o String LeaseContractID
9
o Boolean scrapped default=false
10
--> Participant owner //relationship to a Participant, with the field named 'owner'.
11
--> Participant[] previousOwners optional // Nary relationship
12
o Customer customer
13
}

Imports

We can also import a type from a different namespace with the following

1
import org.example.MyAsset
2
import org.example2.*

Example

We can define a trading network consisting of the following models

  • Asset
    • Name: Commodity
    • ID: Trading Symbol
    • Attributes
      • Trading Symbol
      • Description
      • Main Exchange
      • Quantity
      • Owner (Trader Participant)
  • Participant
    • Name: Trader
    • ID: TradeID
    • First Name
    • Last Name
  • Transaction
    • Name: Trade
    • Commodity (Commodity Asset)
    • New Owner (Trader Participant)

The model file for the above network can be defined as follows

models.cto

1
namespace org.example.mynetwork
2
asset Commodity identified by tradingSymbol {
3
o String tradingSymbol
4
o String description
5
o String mainExchange
6
o Double quantity
7
--> Trader owner
8
}
9
participant Trader identified by tradeId {
10
o String tradeId
11
o String firstName
12
o String lastName
13
}
14
transaction Trade {
15
--> Commodity commodity
16
--> Trader newOwner
17
}

Logic

Logic is defined in Script Files .js which define transaction logic

Example

The logic for a transaction can be defined by a javascript function, in this example, for example, if a transaction occurs in which a Commodity changes Ownership from one Owner to a New Owner, the function can be defined as follows

trade.js

1
/**
2
* Track the trade of a commodity from one trader to another
3
* @param {org.example.mynetwork.Trade} trade - the trade to be processed
4
* @transaction
5
*/
6
async function tradeCommodity(trade) {
7
// Assign the commodity owner to the new owner
8
trade.commodity.owner = trade.newOwner
9
10
// Persist updated asset in asset registry
11
let assetRegistry = await getAssetRegistry('org.example.mynetwork.Commodity')
12
await assetRegistry.update(trade.commodity)
13
}

Queries

Queries are defined in Query File .qry file, note that a single .bna file can only have one query

Access Control

Access Control Files .acl define what permissions different users have, a single network can only have one access control file

Example

Access control files look like the following

1
/**
2
* Access control rules for tutorial-network
3
*/
4
rule Default {
5
description: "Allow all participants access to all resources"
6
participant: "ANY"
7
operation: ALL
8
resource: "org.example.mynetwork.*"
9
action: ALLOW
10
}
11
12
rule SystemACL {
13
description: "System ACL to permit all access"
14
participant: "ANY"
15
operation: ALL
16
resource: "org.hyperledger.composer.system.**"
17
action: ALLOW
18
}

Deployment

We can package our code into a .bna file by running the following command in our directory

Terminal window
1
composer archive create -t dir -n .

We then make use of a PeerAdmin identity card that contains the necessary credential and use this to install the .bna to the network

Terminal window
1
composer network install --card PeerAdmin@hlfv1 --archiveFile tutorial-network@0.0.1.bna