Chapter 4. Smart Contract Development

In this chapter, you will learn about Fabric smart contract development by examining a simple smart contract and the Fabric APIs used to implement Fabric smart contracts. Once you understand the basics of coding a smart contract and the APIs, we can move on to Chapter 5, where we will take the content from this chapter and apply it to invoking smart contracts. To get started, we first need to download the Hyperledger Fabric development tools. They provide a rapid start to developing Fabric smart contracts by encapsulating a complete two-organization Fabric runtime with scripts to bring it up and take it down.

We are going to use the Hyperledger-provided binaries and sample projects from the Fabric project. These binaries and sample projects will help us start a Fabric test network, and the sample projects provide several example smart contracts from which to learn how to develop your own. This chapter examines an example smart contract from a sample project called Fabcar. The binaries we use have the same name on all supported operating systems.

This chapter will help you achieve the following practical goals:

  • Writing a Fabric smart contract by using the JavaScript programming language

  • Installing and instantiating a Fabric smart contract

  • Validating and sanitizing inputs and arguments in a smart contract

  • Creating and running simple or complex queries

  • Working with a private data collection in Fabric

Installing Prerequisites and Setting Up Hyperledger Fabric

Before we can develop Fabric smart contracts, we need to download and install the software required to download Hyperledger Fabric. To download and set up Hyperledger Fabric for developing Fabric smart contracts, we will execute a script that requires certain software to exist on the platform you are developing on—Windows, Linux, or Mac. We need to install Git, cURL, Node.js, npm, Docker and Docker Compose, and the Fabric installation script.

Git

Git is used to clone the fabric-samples repository from GitHub to your local machine. If you don’t have Git installed, you can download it from https://git-scm.com/downloads. Once you download and install it, verify Git installation with the following command:

$ git --version
git version 2.26.2

cURL

We use cURL to download the Fabric binaries from the web. You can download cURL from https://curl.haxx.se/download.html. Once it’s downloaded and installed, verify the installation by executing the following command:

$ curl -V
curl 7.54.0 (x86_64-apple-darwin18.0) libcurl/7.54.0 LibreSSL/2.6.5 zlib/1.2.11 nghttp2/1.24.1
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps
telnet tftp
Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS-
proxy

Node.js and npm

We will be using JavaScript for developing our Fabric smart contracts. Fabric uses Node.js and npm for processing smart contracts. The supported versions of Node.js are 10.15.3 and higher, and 12.13.1 and higher. The supported versions of npm are 6 and higher. Node.js includes npm in the installation. You can download Node.js from https://nodejs.org/en/download. You can verify the installation of Node.js and npm by executing the following commands:

$ node -v
v10.15.3

$ npm -v
6.11.2

Docker and Docker Compose

Hyperledger Fabric consists of several components, each of which operates as a separate executable service, so Fabric maintains Docker images of each component. The images are hosted on the official Docker Hub website. At minimum, you need Docker version 17.06.2-ce. You can get the latest version of Docker at https://www.docker.com/get-started. When Docker is installed, Docker Compose is also installed. You can verify the Docker version by executing the following command:

$ docker -v
Docker version 19.03.13, build 4484c46d9d

Then verify your Docker Compose version by executing this:

$ docker-compose --version
docker-compose version 1.27.4, build 40524192

Before proceeding, start Docker, because Docker needs to be running to complete the installation of the Fabric installation script.

Fabric Installation Script

Create and change to the directory you will use to install the Fabric binaries and sample projects. Docker must be running because the script requires Docker to download the Fabric images.

The script will do the following:

  1. Download the Fabric binaries

  2. Clone fabric-samples from the GitHub repo

  3. Download the Hyperledger Fabric Docker images

Here is the command to execute the script:

curl -sSL https://bit.ly/2ysbOFE | bash -s

But we are not going to execute this command yet. First, we are going to save the command output, so we can examine it. Make sure you are in the directory you have created to install the Fabric binaries and sample projects, and then execute the following command:

curl -sSL https://bit.ly/2ysbOFE > FabricDevInstall.sh

Now you can open FabricDevInstall.sh in your favorite editor and examine the script to see how it clones fabric-samples from GitHub, downloads the Fabric binaries, and downloads the Docker images. Understanding the operation of this script may help you later if you want to customize your Fabric development environment or optimize it based on your workflow.

After you finish examining the script, open a shell and change FabricDevInstall.sh to an executable by executing the following command:

$ chmod +x FabricDevInstall.sh

Now let’s execute FabricDevInstall.sh with the following command:

FabricDevInstall.sh

Once the script completes execution, we should be all set. The fabric-samples directory, Docker images, and Fabric binaries are installed in the fabric-samples/bin directory. In the directory where you ran the command, there should now be a directory called fabric-samples. Everything we need is in fabric-samples. First, we will dig into the Fabcar sample smart contract, which you can find in the fabric-samples directory.

Fundamental Requirements of a Smart Contract

The Fabcar example smart contract is a valid, functional example of a basic smart contract. We have much more to add before it would be ready for production, including security, error management, reporting, monitoring, and testing. We want to remember several important points from this example smart contract. Let’s go through them:

Contract class
Smart contracts extend the Contract class. This is a simple class with few functions. We will look at this class later in the chapter.
Transaction context
All smart contract transaction functions pass a transaction context object as their first argument. This transaction context object is a Context class. When we look at the Contract class, we will look at this class too.
Constructor
All smart contracts must have a constructor. The constructor argument is optional and represents the name of the contract. If not passed, the class name will be used. We recommend you pass a unique name and think of this in terms of a namespace, like a reverse domain name structure.
Transaction function
A transaction function to initialize a smart contract can be created and called prior to client requests. You can use this to set up and execute your smart contract with any required resources. These resources could be tables or maps of data used for lookups, translations, decoding, validating, enriching, security, and so forth.
World state
We can query the world state in multiple ways. The simple query is a key lookup, and a range query gets a set. There is another called a rich query. We look at world state in Chapter 5.
putState
To write data to the ledger, we use the putState function. It takes as arguments a key and value. The value is a byte array, so the ledger can store any data. Typically, we will store the equivalent of business objects that are marshaled into byte arrays prior to being passed as the value argument.
ChaincodeStub
The ChaincodeStub class contains several functions used to interact with the ledger and world state. All smart contracts get an implementation of this class as the stub object contained and exposed by the Context class implementation called ctx, which all transaction functions receive as their first argument.
Read/write transactions
An update in a smart contract is executed in three steps: a read transaction, an update to the in-memory data returned from the read transaction, followed by a write transaction. This creates a new world state for the key while maintaining the history of the key in the immutable file-based ledger.

This point is important to remember: you cannot update (or write to) the ledger and read back what you wrote in the same transaction. It does not matter how many other transaction functions you call from a transaction function. You need to think about the data flow of a transaction request. Clients submit transaction requests to peers, which endorse the request transaction (this is where the smart contract executes); the endorsements with read and write sets are sent back to clients; and endorsed requests are sent to an orderer, which orders the transactions and creates blocks. The orderer sends the ordered requests to commit peers, which validate the read and write sets prior to committing the writes to the ledger and updating the world state.

In the simplest form, a smart contract is a wrapper around ChaincodeStub, because smart contracts must use the interface exposed through this class to interact with the ledger and world state. This is an important point to remember. You should consider implementing business logic in a modular design, treating your Contract subclass like a datasource. This will facilitate evolving your code over time and partitioning logic into functional components that can be shared and reused. In Chapter 5, we look into design in the context of packaging and deploying, and in Chapter 6, we delve into modular design and implementation to facilitate maintenance and testing.

Multiple peers, the endorsing peers, will be executing your smart contracts. Today the architecture places the smart contracts behind a gateway, which is middleware in the smart contract SDK. The gateway receives smart contract requests, processes them, and dispatches them to one or more peers. The peers instantiate the chaincode for execution.

SDK

Fabric provides an SDK implemented in Go, Java, and Node.js (JavaScript) for developing smart contracts. We are interested in the Hyperledger Fabric smart contract development SDK for Node.js, which is called fabric-chaincode-node. While you do not need to download it for smart contract development, you can download or clone fabric-chaincode-node from GitHub.

The fabric-chaincode-node SDK has a lot going on. We are interested in a few components that are central to developing smart contracts. The remaining files are low-level interfaces, support artifacts, tools, and more required to implement the Contract interface with Node.js. This SDK helps developers like us by providing a high-level API so we can learn fast and focus on our smart contract business logic and design.

The first API we are interested in is fabric-contract-api. It is located under the apis subdirectory of fabric-chaincode-node. The other API you see, fabric-shim-api, is the type definition and pure interface for the fabric-shim library, which we look at later in this chapter.

When we start our smart contract project and execute npm install, which we will do in Chapter 5, npm will download fabric-contract-api as a module from the npm public repository as well as fabric-shim. This download happens because we have two explicit smart contract dependencies for developing Hyperledger Fabric smart contracts. These are displayed in this excerpt from the package.json file of the Fabcar smart contract:

"dependencies": {
    "fabric-contract-api": "^2.0.0",
    "fabric-shim": "^2.0.0"
},

fabric-contract-api and fabric-shim are the only modules we need to develop our smart contracts. fabric-contract-api contains the contract.js and context.js files, which implement the Contract and Context classes.

Contract class

Contract is a simple class. Beyond the constructor are utility functions that you can override to implement logic before and after transactions:

constructor(name) {
    this.__isContract = true;
    if (typeof name === 'undefined' || name === null) {
        this.name = this.constructor.name;
    } else {
        this.name = name.trim();
    }
    logger.info('Creating new Contract', name);
}

The beforeTransaction function is called before any contract transaction functions are invoked. You can override this method to implement your own custom logic:

async beforeTransaction(ctx) {
// default implementation is do nothing
}

The afterTransaction function is called after any contract transaction functions are invoked. You can override this method to implement your own custom logic:

async afterTransaction(ctx, result) {
    // default implementation is do nothing
}

The getName function is a getter that returns the contract name:

getName() {
    return this.name;
}

And the createContext function creates a custom transaction context:

createContext() {
    return new Context();
}

Transaction context

You can create a custom transaction context to store your own objects that your functions can access through the ctx object, which all transaction functions receive as their first argument. Here is an example of creating a custom context:

const AssetList = require('./assetlist.js');

class MyContext extends Context {
    constructor() {
        super();
        this.assetList = new AssetList(this);
    }

}

class AssetContract extends Contract {
    constructor() {
        super('org.my.asset');
    }

    createContext() {
        return new MyContext();
    }
}

With the custom context MyContext, transactions can access assetList as ctx.assetList.

As you can see, creating a simple, smart contract is easy. You import Contract from fabric-contract-api and extend it. Then create a no-argument constructor and export our contract. That’s it.

Context class

OK, that’s the Contract class, but what about the Context class? You just learned how to create a custom transaction context, but what does the Context class contain? As you have learned, every transaction function gets a Context object called ctx as its first argument. This is the transaction context. It contains two important objects: stub and clientIdentity. The stub is a ChaincodeStub class implementation, and clientIdentity is a ClientIdentity class implementation. We discuss these classes next.

ChaincodeStub has all the functions we need to interact with the ledger and world state. It is our API for the ledger and world state. It is contained in the fabric-shim module under the lib directory and implemented in the stub.js file. The two primary functions are getState and putState. Several additional functions are available. Most can be grouped into the following categories:

  • State related

  • Query related

  • Transaction related

  • Private data related

These four groups represent most of the functions. The state-related functions are used to read from and write to the ledger. They use or involve the use of a key.

The query-related functions are two rich query functions, one with pagination. Rich queries are string queries native to the database. To use rich queries, you need to use CouchDB for the database. We will do this in Chapter 5, where you’ll learn about invoking smart contracts. Another unique query function is getHistoryForKey, which takes a key and returns the history for it. This can be used to audit changes and find transactions that failed.

Hyperledger has five transaction-related functions:

  • getTxID(): string;

  • getChannelID(): string;

  • getCreator(): SerializedIdentity;

  • getMspID(): string;

  • getTransient(): Map<string, Uint8Array>;

Use getTxID to retrieve the transaction ID, getChannelID for the channel’s ID, getCreator for the client, getMspID for the organization the client belongs to, and getTransient for private data. We will execute each of these in the next two chapters.

Hyperledger has nine private data–related functions:

  • getPrivateData(collection: string, key: string): Promise​<Uint8Array>;

  • getPrivateDataHash(collection: string, key: string): Promise​<Uint8Array>;

  • putPrivateData(collection: string, key: string, value: Uint8Array): Promise<void>;

  • deletePrivateData(collection: string, key: string): Promise<void>;

  • setPrivateDataValidationParameter(collection: string, key: string, ep: Uint8Array): Promise<void>;

  • getPrivateDataValidationParameter(collection: string, key: string): Promise<Uint8Array>;

  • getPrivateDataByRange(collection: string, startKey: string, endKey: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;

  • getPrivateDataByPartialCompositeKey(collection: string, objectType: string, attributes: string[]): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;

  • getPrivateDataQueryResult(collection: string, query: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;

These nine private data functions provide the ability to read and write to a private data collection, get a private data hash, delete from private data, set and get an endorsement policy for private data validation, and get private data by range, partial composite key, or rich query. We will execute some of these in Chapters 5 and 6 when we employ the use of private data with our smart contract transactions.

Now that you have a good idea of what sort of functionality is available through the stub object we get from the ctx object, let’s look at ClientIdentity, the other object in the transaction context.

The clientIdentity object contained in the transaction context as ctx.clientIdentity is the implementation of the ClientIdentity class, which is a small class with only five functions:

  • assertAttributeValue(attrName: string, attrValue: string): boolean;

  • getAttributeValue(attrName: string): string | null;

  • getID(): string;

  • getIDBytes(): Uint8Array;

  • getMSPID(): string;

The assertAttributeValue and getAttributeValue functions operate on the client certificate. Using these functions, granular security can be implemented by employing the certificate attribute values. The getID and getIDBytes functions retrieve the client’s identity, and getMSPID is used to get the organization the client belongs to. Using these functions, you can implement a wide range of authentication and authorization design patterns.

Transaction functions

Transaction functions are the smart contract functions that clients call. These are the business functions you design and implement in your smart contracts. Here is an example of three transaction functions from the Fabcar smart contract. We will define them in “Defining a Smart Contract”:

async queryCar(ctx, carNumber) 
async createCar(ctx, carNumber, make, model, color, owner)
async queryAllCars(ctx)

All transaction functions receive as the first argument the transaction context, the ctx object. Transaction functions use this object to reference the stub and clientIdentity objects—for example, ctx.stub and ctx.clientIdentity. The stub is an instance of ChaincodeStub. The clientIdentity is an implementation of C⁠l⁠i⁠e⁠n⁠t​I⁠d⁠e⁠n⁠t⁠i⁠t⁠y and exposes functions for getting the transaction ID, client ID, any client attributes, and the organization ID. These functions can be used for application- and transaction-specific authentication and authorization.

It is common for most transaction functions to contain a call to the ledger or world state. The stub provides the functions for reading from the world state and writing to the ledger, which updates the world state.

The way you design your transaction functions is completely under your control. You can group them, mix them, create libraries, and more. Remember, for transactions that write, all of the designated endorsing peers must execute your smart contracts.

Endorsement policies determine whether a transaction gets committed. For example, an endorsement policy might state that three out of four peers must endorse the transaction. If, for some reason, fewer than three peers can endorse the transaction, then the transaction will not get committed, meaning the data will not be available on the ledger.

A point to remember is that even though a write transaction may fail, it will be flagged as invalid and written to the ledger. An invalid transaction will not be part of the world state.

As a best practice, Fabric smart contracts demand deterministic code. Many peers need to execute the code, and they all need to arrive at the same result. Therefore, the inputs must always return the same result. It must not matter what peer executes the code. Given the same inputs, the peer should return the same results. This should happen no matter the number of times the code is executed.

The code should have a beginning and an end. It should never depend on dynamic data or long random executions. The code should be fast and efficient, with clear logic flows that are not circular. For example:

async CreateAsset(ctx, id, amount, owner) {
        const asset = {
            ID: id,
            Amount: amount,
            Owner: owner
        };
        return ctx.stub.putState(id,Buffer.from(
           JSON.stringify(asset)));
}

When creating an asset, we will expect the asset ID, amount, and owner as inputs.

Validate and sanitize arguments

Transactions must validate and sanitize their arguments. This is not unique to smart contracts. It is a wise default practice if you want to ensure the integrity and availability of your smart contract.

Employ known techniques for validating your arguments and preventing any data that may harm your smart contract. You also want to sanitize your arguments and ensure the quality of the data you expect. You want to limit unnecessary processing of data that will later in your logic cause a failure or edge case not covered. Here is an example that checks whether the function is Process; if it is not, we will throw an exception:

func (c *Asset) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    function, args := stub.GetFunctionAndParameters()
    if function == "Process" {
        return c.Process(stub, args)
    } 
    return shim.Error("Invalid function name")
}

Simple state interaction (get, put, delete)

Fabric smart contracts at the core are state machines that evolve over time, keeping an immutable history of all prior states. The three primary stub functions you will use are the following:

getState(key: string): Promise<Uint8Array>;
putState(key: string, value: Uint8Array): Promise<void>;
deleteState(key: string): Promise<void>;

The stub functions provide your smart contracts the functionality to read from the world state, write to the ledger, and delete from the world state.

The ledger and world state are key-value data stores. This keeps it simple and easy but allows rich data to be stored on the ledger, queried, and viewed by the world state, a document, or NoSQL database. Currently, LevelDB and CouchDB are supported. LevelDB is a simple key-value data store, while CouchDB is a rich and robust NoSQL document database. This means you can read and write simple to complex JSON data structures to the ledger and query the world state database for rich data. Several functions are available for queries.

Create and Execute Queries

Smart contracts often need to look up or query data from the world state while processing a transaction. Remember the update to our Fabcar sample smart contract? To perform an update, a smart contract typically must find and load the existing object to update. Then it updates the in-memory data and writes the updated data to the ledger—remember putState to write and getState to read.

Unlike a relational database in which we can update a field without selecting the row first, with Fabric smart contracts, we must load the value of a key and update the value. That may be a single field in a very large and complex JSON data structure, or it may be a simple object with one field, the field we are updating. Why is this? Because all data is tied to a unique key. If a key’s associated value is an object with four fields, we think of each field as a value—but to the ledger, it is all just one value object identified by a single unique key.

Here are the available stub functions you can use for finding data:

  • getState(key: string): Promise<Uint8Array>;

  • getStateByRange(startKey: string, endKey: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;

  • getStateByRangeWithPagination(startKey: string, endKey: string, pageSize: number, bookmark?: string): Promise<StateQueryResponse<Iterators.StateQueryIterator>> & AsyncIterable<Iterators.KV>;

  • getQueryResult(query: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV>;

  • getQueryResultWithPagination(query: string, pageSize: number, bookmark?: string): Promise<StateQueryResponse<Iterators.StateQueryIterator>> & AsyncIterable<Iterators.KV>;

  • getHistoryForKey(key: string): Promise<Iterators.HistoryQueryIterator> & AsyncIterable<Iterators.KeyModification>;

We discuss these in Chapter 5, when we use them in a smart contract and invoke them from a client.

Defining a Smart Contract

Let’s start with the simple Fabcar smart contract. In the fabric-samples directory, locate the chaincode directory. In the chaincode directory, locate the fabcar directory. In the fabcar directory, locate the javascript directory. Change to this directory in your shell and execute the following command:

$ npm install

This will create the node_modules directory and install the dependent modules defined in package.json. We did this because we depend on the fabric-contract-api and fabric-shim modules. These are the two modules we use when developing Fabric smart contracts in JavaScript. We will look at these after we examine the Fabcar smart contract.

Now let’s examine the Fabcar smart contract. This simple smart contract is a great example for learning Fabric smart contract development because it contains necessary details to form a foundation from which we can move on to more advanced smart contracts. It is located in the lib directory in the current directory, which should be the fabric-samples/chaincode/fabcar/javascript directory. Open fabcar.js in your favorite editor; Example 4-1 shows the source code.

Example 4-1. fabcar.js
/*
 * Copyright IBM Corp. All Rights Reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 */
'use strict';
const { Contract } = require('fabric-contract-api'); 1
class FabCar extends Contract { 2
    async initLedger(ctx) { 3
        console.info('============= START : Initialize Ledger ===========');

        const cars = [ 4
            {
                color: 'blue',
                make: 'Toyota',
                model: 'Prius',
                owner: 'Tomoko',
            },
...
        ];
        for (let i = 0; i < cars.length; i++) { 5
            cars[i].docType = 'car';
            await ctx.stub.putState('CAR' + i, 
            Buffer.from(JSON.stringify(cars[i]))); 6
        }
    }
    async queryCar(ctx, carNumber) { 7
        const carAsBytes = await ctx.stub.getState(
        carNumber); // get the car from chaincode state 8
        if (!carAsBytes || carAsBytes.length === 0) {
            throw new Error(`${carNumber} does not exist`);
        return carAsBytes.toString();
    }
    async createCar(ctx, carNumber, make, model, color, owner) { 9
        console.info('============= START : Create Car ===========');
        const car = {color, docType: 'car',make, model, owner}

        await ctx.stub.putState(carNumber, Buffer.from(
        JSON.stringify(car))); 10
    }
    async queryAllCars(ctx) { 11
        const startKey = '';
        const endKey = '';
        const allResults = [];
        for await (const {key, value} of ctx.stub.getStateByRange(
            startKey, endKey)) { 12
            const strValue = Buffer.from(value).toString('utf8');

            let record;
            try {
                record = JSON.parse(strValue);
            } catch (err) {
                record = strValue;
            }
            allResults.push({ Key: key, Record: record });
        }
        return JSON.stringify(allResults);
    }
    async changeCarOwner(ctx, carNumber, newOwner) { 13
        

const carAsBytes = await ctx.stub.getState(carNumber); 14
if (!carAsBytes || carAsBytes.length === 0) {
            throw new Error(`${carNumber} does not exist`);
        }
        const car = JSON.parse(carAsBytes.toString());
        car.owner = newOwner;
        await ctx.stub.putState(carNumber, Buffer.from(
JSON.stringify(car))); 15
    }
}
module.exports = FabCar; 16
1

We start by importing the fabric-contract-api module.

2

All Fabric smart contracts extend the Contract class. We get the Contract class from the fabric-contract-api module we imported in line 1.

3

Smart contracts can use transactions to initialize them prior to processing client application requests. This line is the beginning of the function that initializes the smart contract. All smart contract functions receive a transaction context object as an argument, called by convention ctx.

4

In this example, the initLedger function is creating an array of objects called cars. Each array object contains key-value pairs. You can think of the array of objects as records of assets, and the object key-value pairs as the fields. This function effectively preloads an array of car objects for exercising the transaction functions in the smart contract.

5

Next, the initLedger function is iterating through the array of car asset objects and adding a field called docType to each object, and assigning the string value car to each object.

6

This line is the first use of the ctx object (Context class) passed as the first function argument to all Contract class transaction functions. The ctx object contains the stub object, which is a ChaincodeStub class. The ChaincodeStub class implements an API to access the ledger. This line calls the putState function, which writes the key and value to the ledger and world state.

Note

Hyperledger Fabric implements the blockchain ledger in two components: a file-based component and a database component. The file-based component is the low-level immutable data structure implementing the ledger, and the database component exposes the current state of the file-based ledger. The database component is called the world state because it represents the current state of the ledger. The file-based component maintains the perpetual immutable ledger. The fabric-contact-api accesses the world state. Lower-level APIs access the file-based ledger.

7

The first transaction function comes next. As stated earlier, the first argument of all smart contract transaction functions is the ctx object, which represents the transaction context and is a Context class. Any other arguments are optional.

8

The queryCars function is a read transaction. Using the ctx object, it calls the stub’s getState function, which will read from the world state—the database. The stub is an implementation of the ChaincodeStub class, which we will cover later. For this function, the argument called carNumber is the key passed to the getState function, which will search the world state database for the key and return the associated value stored for it. The remainder of the function checks whether data was returned and, if so, converts the byte array returned into a string and returns the string that represents the value of the key stored in the world state. Remember, the world state is a representation of the perpetual immutable file-based ledger’s current state for any key-value pair stored in the ledger. While the database may be mutable, the file-based ledger is not. So even when a key-value pair is deleted from the database or world state, the key-value pair is still in the file-based ledger where all history is maintained in the perpetual immutable ledger.

9

Then we have the second transaction function. It passes the values required to create a new car record object, which we will add to the ledger.

10

With the car record object built from the function arguments, we call the ChaincodeStub API function implemented by stub, called putState, which will write the key and value to the ledger and update the current world state. The first two arguments that pass to the putState function are a key and a value, respectively. We need to change the value, the car record object, into a byte array, which ChaincodeStub APIs require.

11

The next transaction function, called queryAllCars, is a read transaction and demonstrates a range query. A range query, like all queries, is executed by the peer that receives the request. A range query takes two arguments: the beginning key and the ending key. These two keys represent the beginning and end of the range. All keys that fall into the range are returned along with their associated values. You can pass an empty string for both keys to retrieve all keys and values.

12

A for loop is executed, which stores all the keys and associated values returned from the ChaincodeStub API function getStateByRange.

13

The last transaction function, changeCarOwner, combines both read and write tasks to change the world state. The business logic here is a transfer of ownership. In addition to the ctx argument, two arguments are passed: a key called carNumber, and a value object called newOwner.

14

Next, we need to retrieve the record object from the world state, which represents the current key and value for this record. The key is carNumber. We use it to execute the ChaincodeStub API getState. Once we retrieve the current car record object for carNumber, we change the owner field to newOwner.

15

After retrieving the ledger data representing the world state and updating the retrieved data, we update the ledger for this car record object by executing the ChaincodeStub API putState. This writes a new key and value to the ledger that represents the world state. If the car record object is now retrieved, the ledger will not show the new owner until the record object is committed to the ledger. It is important to understand that once committed, the ledger is appended, and the database state will be changed (the world state will be updated). You can think of the ledger as an ever-growing stack of objects, each with a unique identifier called the key. There can be many keys with the same value, but only one represents the current or world state. This is how the database implements the view of the world state, while the file-based ledger implements the immutable history of all keys in timestamp order.

Note

The file-based ledger stores all write transactions. Both successful and unsuccessful write transactions are part of the file-based immutable ledger. Flags control the validity of transactions stored in the immutable file-based ledger. This facilitates an audit of all submitted write transactions.

16

This line is Node.js specific. We discuss exporting smart contract modules in Chapter 5, when we cover smart contract execution, including project structure, packaging, and deployment.

This completes this simple smart contract. We will now discuss it, in summary, to point out the fundamental requirements to develop a smart contract. From this basic smart contract, complex smart contract applications can be designed and developed.

Define Assets by Using Key-Value Pairs

When designing smart contracts, you may need to think in terms of assets. Assets are generic and can represent many things, including tangible and intangible objects. They could be machine parts, dog food, currency, or green derivatives. We use name-value pairs, or key-value pairs, depending on how you want to think about it, to create our data structures. Here is an example we can discuss:

const assets = [
    {
        color: 'blue',
        make: 'Honda',
        model: 'Accord',
        owner: 'Jones',
    },  
    {
        color: 'red',
        make: 'Ford',
        model: 'Mustang',
        owner: 'Smith',
    },
];
for (let i = 0; i < assets.length; i++) { 
    assets[i].docType = 'asset';
    await ctx.stub.putState('ASSET' + i,
      Buffer.from(JSON.stringify(assets[i])));
}

We’ve seen this before in the Fabcar example. It is a good example of basic processing, from which you can advance based on your unique use case. This example creates an array of objects that represent assets. The key-value pairs define the attributes, or characteristics, of each asset. The array acts as a simple database of assets. Each asset is written to the ledger by calling ctx.stub.putState, which takes a key and value. The value must be a byte array, so we convert the JSON object to a string and then convert the string to the byte array. You will do this a lot and may want to simplify it and start building a utility or library. This particular code was used to initialize the smart contract.

We can also define assets by using a smart contract transaction. The createAsset transaction function shown next illustrates how simple it is to create an asset and write it to the ledger. This function would be called by a client. The client could be a user or process. What’s important to remember is the asset will not be available until the transaction is committed to the ledger. So you can’t write a bunch of assets to the ledger and later in your smart contract expect to read and use their data to continue processing. This disconnected state is something to think about when you begin designing and brainstorming. Here’s the createAsset transaction function:

async createAsset(ctx, assetNumber, make, model, color, owner) {
        const asset = {
        color,
        docType: 'asset',
        make,
        model,
        owner,
        };
    await  ctx.stub.putState(assetNumber, Buffer.from(JSON.stringify(asset)));
}

Collect Private Data

The private data collection (PDC) is a partition of ledger data belonging to an organization that stores private data and keeps data private from other organizations on that channel. This includes private data and the hash value of private data. Chapter 9 provides more details. The need to keep specific data private is important to developing smart contracts. Many smart contracts need to be compliant with privacy and security requirements. Fabric supports private data for transaction functions and smart contracts.

The private data can be shared or kept isolated and secure. We can expire the private data after a number of blocks are created or on demand. The data placed into the PDCs remains separate from the public data, and PDCs are local and protected. The world state can be used in conjunction with PDCs by the use of hashes as well as public keys.

Table 4-1 lists several functions that are available from stub.

Table 4-1. Commands for working with private data
API Note
getPrivateData(collection: string, key: string): Promise<Uint8Array> Returns the endorsement policy from the collection name and the specified key.
putPrivateData(collection: string, key: string, value: Uint8Array): Promise<void> Puts the collection name and the specified key and value into the transaction’s private writeSet.
deletePrivateData(collection: string, key: string): Promise<void> Deletes the endorsement policy by providing the collection name and private data variable key.
setPrivateDataValidationParameter(collection: string, key: string, ep: Uint8Array): Promise<void> Sets the endorsement policy by providing the collection name and private data variable key.
getPrivateDataValidationParameter(collection: string, key: string): Promise<Uint8Array> Returns the endorsement policy by providing the collection name and private data variable key.
getPrivateDataByRange(collection: string, startKey: string, endKey: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV> Returns the endorsement policy from the collection name and the private data variable key.
getPrivateDataByPartialCompositeKey(collection: string, objectType: string, attributes: string[]): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV> Queries the endorsement policy in a given collection name and a given partial composite key.
getPrivateDataQueryResult(collection: string, query: string): Promise<Iterators.StateQueryIterator> & AsyncIterable<Iterators.KV> Performs a rich query against a given private collection. It is supported for state databases that can run a rich query (e.g., CouchDB).

Employing private data can be tricky, and patterns exist for using it under differing circumstances. We will cover most of these functions in Chapter 5, when we implement private data functions to invoke, and again in Chapter 6, when we use them in maintenance and testing.

Set Attribute-Based Access Control

Eventually, you will need a way to implement granular authentication and authorization. Clients have an identity that controls access. The identities must belong to authorized organizations, and organizations belong to channels that host chaincode. A certificate represents the client’s identity. It supports attributes that can be used to implement transactions and smart-contract-level authentication control and authorization policies.

You access this information from the clientIdentity object contained in the transaction context. This object has two functions related to attribute values:

assertAttributeValue(attrName: string, attrValue: string): boolean;
getAttributeValue(attrName: string): string | null;

Use assertAttributeValue to check for the presence of an attribute and use getAttributeValue to retrieve a specific attribute. It is good practice to assert the attribute before retrieving it. In Chapters 5 and 6, we will employ attributes for security and other purposes.

Initialize the Ledger State

The initialization of the ledger is often a required task. The following example from the Fabcar code we looked at earlier illustrates how to initialize your smart contract state. You will initialize it right after it has been committed. After that, you can start to submit transactions and query ledger data by invoking smart contract methods.

You create a function that you will call to execute your initialization; here, it is called initLedger. In the initLedger function, you can perform what you need to do to initialize. In this example, we create an array of business objects, loop through the cars array, and then add an additional attribute docType to each car object in the cars array. Here is the initLedger logic:

async initLedger(ctx) {
    const cars = [
         {
              color: 'blue',
              make: 'Toyota',
              model: 'Prius',
              owner: 'Tomoko',
         },
         .
         .
         .
         {
              color: 'brown',
              make: 'Holden',
              model: 'Barina',
              owner: 'Shotaro',
              },
    ];
    for (let i = 0; i < cars.length; i++) {
              cars[i].docType = 'car';
              await ctx.stub.putState('CAR' + i,
              Buffer.from(JSON.stringify(cars[i])));
              console.info('Added <--> ', cars[i]);
    }
}

The initLedger function writes the array objects to the ledger by using the putState function. To execute the initLedger function, we need to invoke the smart contract. We can use the peer CLI invoke command. Let’s take a look at how we can call initLedger through the invoke command.

Chaincode invoke init

To execute a transaction function on our smart contract, we can use the invoke command provided by the peer binary. This binary offers many commands, several of which you will learn in Chapters 5 and 6. Here we use it to invoke our initLedger function:

Note

We have printed the following command on multiple lines for readability. When you type in the command, it must be on one line, or it will fail to execute.

peer chaincode invoke 
-o localhost:7050 
--ordererTLSHostnameOverride orderer.example.com 
--tls true 
--cafile /OReilly/fabric-samples/test-network/organizations/ordererOrganizations
/example.com/orderers/orderer.example.com/msp/tlscacerts/tls/
ca.example.com-cert.pem 
-C mychannel 
-n fabcar 
--peerAddresses localhost:7051 
--tlsRootCertFiles /OReilly/fabric-samples/test-network/organizations
/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt 
--isInit 
-c '{"function":"initLedger","Args":[]}'

[chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200

The invoke command executes the command object following the -c command argument flag. The command object specifies the function to execute and any function arguments. Here we are executing the initLedger function, and there are no function arguments.

We can test the results of the initLedger function. We expect to return the contents of the array written to the ledger. We will use the query command; let’s look at how we can query ledger data.

Chaincode query

Using the peer’s query command, we can execute one of our smart contract query functions. In this case, we set the -c command flag to execute queryAllCars:

peer chaincode query 
-C mychannel 
-n fabcar 
-c '{"Args":["queryAllCars"]}'

Here is the return output:

[{"Key":"CAR0","Record":{"color":"blue","docType":"car",
"make":"Toyota","model":"Prius","owner":"Tomoko"}},
.
.
.
{"Key":"CAR9","Record":{"color":"brown","docType":"car","make":"Holden",
"model":"Barina","owner":"Shotaro"}}]

The result shows the initLedger function executed, and our smart contract is initialized.

Installing and Instantiating a Smart Contract

In preparation for Chapter 5, let’s go over what we need to do once we finish coding our smart contract. This section discusses several steps that we need to perform to reach a point where we can invoke our smart contract either from the command line or from a smart contract client. These steps are as follows:

  1. Package the chaincode.

  2. Install the chaincode.

  3. Query the installation.

  4. Approve the package.

  5. Check commit readiness.

  6. Commit the chaincode definition.

  7. Query whether the chaincode is committed.

  8. Initialize the contract.

  9. Execute a query.

Chapter 5 covers these steps in more detail. They contain example command-line code, and some have output. The following peer commands can be referenced in the Hyperledger Fabric documentation.

Package the Chaincode

The first thing we need to do is package our code. As you can see from the following command, we use the peer CLI to perform this step and all remaining steps.

To prepare our smart contract, we use the following peer package command:

peer lifecycle chaincode package fabcar.tar.gz \ 
        --path ../chaincode/fabcar/javascript/ \
        --lang node \
        --label fabcar_1

Once this command completes, we have a tar.gz file containing our smart contract. Next, we need to install this package.

Install the Chaincode

After we have packaged our smart contract, we can install it. If several organizations are collaborating, there is no need for all organizations to package smart contracts separately. One smart contract package can be used by all organizations. Once an organization receives the package, it is installed on their endorsing peers. Chapter 9 covers this in more detail.

Here is the installation command, which shows a successful output message:

peer lifecycle chaincode install fabcar.tar.gz

[cli.lifecycle.chaincode] submitInstallProposal -> INFO 001 Installed

Once the contract is installed, you may want to verify it.

Query the Installation

You can execute the following command to get the details of the latest chaincode installed:

peer lifecycle chaincode queryinstalled

Installed chaincodes on peer:
Package ID: fabcar_1:5a00a40697…330bf5de39, 
Label: fabcar_1

You may receive many package IDs, depending on the number of installed packages. You can use a script to filter the output if you need to automate a task that is dependent on a particular package being installed or not installed. Once a chaincode package is installed, it must be approved.

Approve the Package

After installing a package, an organization must approve it before it can be committed and accessed. This command has a lot of parameters. The one of most interest, for our purposes, is –package-id. We can get it from the output of the preceding queryinstalled command. package-id is used as the identifier for the chaincode installation package:

peer lifecycle chaincode approveformyorg 
-o localhost:7050 
--ordererTLSHostnameOverride orderer.example.com 
--tls true 
--cafile /OReilly/fabric-samples/test-network/organizations/ordererOrganizations
/example.com/orderers/orderer.example.com/msp/tlscacerts/
tlsca.example.com-cert.pem 
--channelID mychannel 
--name fabcar 
--version 1 
--init-required 
--package-id fabcar_1:5a00a406972168ac5856857b5867f51d5244208b876206b7e0e418330bf5de39 
--sequence 1

Once the approve command completes, we are ready to determine whether we can commit it. We use the checkcommitreadiness command.

Check Commit Readiness

A number of organizations must approve the chaincode package before it can be committed. The number depends on the policy, which could demand that all organizations or a subset of organizations approve. Ideally, you want all organizations to approve. We can use the checkcommitreadiness command to determine whether we can commit the package. In this case, we cannot because Org2 has not approved yet. Once it does, this command will show true for Org1 and true for Org2:

peer lifecycle chaincode checkcommitreadiness 
--channelID mychannel 
--name fabcar 
--version 1 
--sequence 1 
--output json 
--init-required

{
    "approvals": {
        "Org1MSP": true,
        "Org2MSP": false
    }
}

In a Hyperledger configuration, we can define different types of life-cycle endorsement policies. The default is MAJORITY Endorsement. This requires a majority of the peers to endorse a chaincode transaction for validation and execution in the channel and commit the transaction to the ledger. Chapter 9 covers this in more detail. Once all approvals are true, we can commit the chaincode package.

Commit the Chaincode Definition

Once all organizations or subsets of the organization have been approved to satisfy the policies mentioned, the chaincode can be committed to the ledger. To commit the chaincode, we use the commit command shown here:

peer lifecycle chaincode commit 
-o localhost:7050 
--ordererTLSHostnameOverride orderer.example.com 
--tls true 
--cafile /OReilly/fabric-samples/test-network/organizations/ordererOrganizations
/example.com/orderers/orderer.example.com/msp/tlscacerts/
tlsca.example.com-cert.pem 
--channelID mychannel 
--name fabcar 
--peerAddresses localhost:7051 
--tlsRootCertFiles /OReilly/fabric-samples/test-network/organizations
/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
--version 1 
--sequence 1
--init-required

Here is the output after the command runs:

[chaincodeCmd] ClientWait -> INFO 001 txid
[ef59101c320469be3242daa9ebe262771fc8cc8bd5cd0854c6424e1d2a0c61c2] committed with status (VALID) at
localhost:9051

Next, we can check whether the chaincode is committed with querycommitted.

Query Whether the Chaincode Is Committed

Chaincode must be committed before it can be invoked. To determine whether chaincode is committed, use the querycommitted chaincode command:

peer lifecycle chaincode querycommitted 
--channelID mychannel 
--name fabcar

Here is the output:

Committed chaincode definition for chaincode 'fabcar' on channel 'mychannel':
Version: 1, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc, Approvals: [Org1MSP: true,
Org2MSP: true]

Running the querycommitted command tells us our chaincode is ready. This chaincode contains a smart contract that needs initialization. To initialize it, we will invoke it.

Initialize the Contract

We can finally initialize our smart contract because the chaincode is approved and committed. We can use the invoke command to execute smart contract transactions:

peer chaincode invoke 
-o localhost:7050 
--ordererTLSHostnameOverride orderer.example.com 
--tls true 
--cafile /OReilly/fabric-samples/test-network/organizations/ordererOrganizations
/example.com/orderers/orderer.example.com/msp/tlscacerts/
tlsca.example.com-cert.pem 
-C mychannel 
-n fabcar 
--peerAddresses localhost:7051 
--tlsRootCertFiles /OReilly/fabric-samples/test-
network/organizations/peerOrganizations/org1.example.com/peers
/peer0.org1.example.com/tls/ca.crt 
--peerAddresses localhost:9051 
--tlsRootCertFiles /OReilly/fabric-samples/test-network/organizations
/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt 
--isInit
-c '{"function":"initLedger","Args":[]}'

After executing the invoke command, we will see the following output; it returns a 200 response status if chaincode invocation is successful:

[chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200

At the bottom of the invoke command, we see the -c command flag and command object, which contains the function key and value initLedger with no arguments. This means the smart contract transaction function initLedger will be executed. The output shows a successful result. Our smart contract is now initialized and ready for clients. We can test our smart contract now by executing a query.

Execute a Query

We have gone through the steps to take your smart contract source-code project and instantiate it. We can test it by executing a query like this one:

peer chaincode query 
-C mychannel 
-n fabcar 
-c '{"Args":["queryAllCars"]}'

Here is the output after executing the queryAllCars command:

[{"Key":"CAR0","Record":{"color":"blue","docType":"car",
"make":"Toyota","model":"Prius","owner":"Tomoko"}},
.
.
.
{"Key":"CAR9","Record":{"color":"brown","docType":"car",
"make":"Holden","model":"Barina","owner":"Shotaro"}}]

This query executes the smart contract transaction function called queryAllCars. Writes get committed and thus require endorsing, which involves several peers. A query should not have to task more than one peer for execution. This is what the client-side code does for us. This example illustrates how a transaction function wraps a ChaincodeStub function, in this case a rangeQuery abstracted as queryAllCars.

Summary

We began by setting up our Hyperledger Fabric development environment in preparation for Chapters 5 and 6 and using it to explore and examine a simple but complete smart contract called Fabcar. The code we write for Fabric smart contracts depends on the APIs provided by the SDKs. We covered the Fabcar code because it is small and simple to learn. This allowed us to focus on the code of smart contracts, the classes and interfaces employed, and the Fabric smart contract APIs we depend on.

Fabric smart contract SDKs are available for JavaScript, Java, and Go, with more coming. We used the JavaScript Fabric smart contract SDK for Node.js. Using it allowed us to explore fabric-contract-api, and the core classes and objects we need to develop Fabric smart contracts.

With knowledge of the API, we covered how to create a smart contract and what smart contract transaction functions are. Functions execute our smart contract transactions, so it was important to introduce several important topics like validating and sanitizing function arguments, initializing smart contracts, and interacting with the ledger. The Fabric contract API provides the interface to the ledger, which you learned how to access in our smart contracts through the transaction context every transaction receives. There was a lot to cover, but we tried to keep it simple yet provide you with exposure to the fabric-contract-api, which contains the interfaces you need to design and implement robust smart contracts.

Once the smart contract code is written, we need to package and deploy it to the Fabric network. This requires several steps to accomplish. Step by step, we went through each one. It is important to know these steps to take our smart contracts from source code to instantiated chaincode. We can execute only instantiated code.

In Chapters 5 and 6, we’ll package and instantiate the smart contracts we create by using the knowledge you learned in this chapter. Now we can move on to Chapter 5 and focus on the invocation of smart contracts.

Get Hands-On Smart Contract Development with Hyperledger Fabric V2 now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.