Writing Your First Application

The goal of this document is to show the tasks and provide a baseline for writing your first application against a Hyperledger Fabric network.

At the most basic level, applications on a blockchain network are what enable users to query a ledger (asking for specific records it contains), or to update it (adding records to it).

Our application, composed in Javascript, leverages the Node.js SDK to interact with the network (where our ledger exists). This tutorial will guide you through the three steps involved in writing your first application.

1. Starting a test Hyperledger Fabric blockchain network. We need some basic components in our network in order to query and update the ledger. These components – a peer node, ordering node and Certificate Authority – serve as the backbone of our network; we’ll also have a CLI container used for a few administrative commands. A single script will download and launch this test network.

2. Learning the parameters of the sample smart contract our app will use. Our smart contracts contain various functions that allow us to interact with the ledger in different ways. For example, we can read data holistically or on a more granular level.

3. Developing the application to be able to query and update records. We provide two sample applications – one for querying the ledger and another for updating it. Our apps will use the SDK APIs to interact with the network and ultimately call these functions.

After completing this tutorial, you should have a basic understanding of how an application, using the Hyperledger Fabric SDK for Node.js, is programmed in conjunction with a smart contract to interact with the ledger on a Hyperledger Fabric network.

First, let’s launch our test network…

Getting a Test Network

Visit the Prerequisites page and ensure you have the necessary dependencies installed on your machine.

Now determine a working directory where you want to clone the fabric-samples repo. Issue the clone command and change into the fabcar subdirectory

git clone https://github.com/hyperledger/fabric-samples.git
cd fabric-samples/fabcar

This subdirectory – fabcar – contains the scripts and application code to run the sample app. Issue an ls from this directory. You should see the following:

chaincode    invoke.js       network         package.json    query.js        startFabric.sh

Now use the startFabric.sh script to launch the network.

Note

The following command downloads and extracts the Hyperledger Fabric Docker images, so it will take a few minutes to complete.

./startFabric.sh

For the sake of brevity, we won’t delve into the details of what’s happening with this command. Here’s a quick synopsis:

  • launches a peer node, ordering node, Certificate Authority and CLI container
  • creates a channel and joins the peer to the channel
  • installs smart contract (i.e. chaincode) onto the peer’s file system and instantiates said chaincode on the channel; instantiate starts a chaincode container
  • calls the initLedger function to populate the channel ledger with 10 unique cars

Note

These operations will typically be done by an organizational or peer admin. The script uses the CLI to execute these commands, however there is support in the SDK as well. Refer to the Hyperledger Fabric Node SDK repo for example scripts.

Issue a docker ps command to reveal the processes started by the startFabric.sh script. You can learn more about the details and mechanics of these operations in the Building Your First Network section. Here we’ll just focus on the application. The following picture provides a simplistic representation of how the application interacts with the Hyperledger Fabric network.

_images/AppConceptsOverview.png

Alright, now that you’ve got a sample network and some code, let’s take a look at how the different pieces fit together.

How Applications Interact with the Network

Applications use APIs to invoke smart contracts (referred to as “chaincode”). These smart contracts are hosted in the network and identified by name and version. For example, our chaincode container is titled - dev-peer0.org1.example.com-fabcar-1.0 - where the name is fabcar, the version is 1.0 and the peer it is running against is dev-peer0.org1.example.com.

APIs are accessible with a software development kit (SDK). For purposes of this exercise, we’ll be using the Hyperledger Fabric Node SDK though there is also a Java SDK and CLI that can be used to develop applications.

Querying the Ledger

Queries are how you read data from the ledger. You can query for the value of a single key, multiple keys, or – if the ledger is written in a rich data storage format like JSON – perform complex searches against it (looking for all assets that contain certain keywords, for example).

_images/QueryingtheLedger.png

As we said earlier, our sample network has an active chaincode container and a ledger that has been primed with 10 different cars. We also have some sample Javascript code - query.js - in the fabcar directory that can be used to query the ledger for details on the cars.

Before we take a look at how that app works, we need to install the SDK node modules in order for our program to function. From your fabcar directory, issue the following:

npm install

Note

You will issue all subsequent commands from the fabcar directory.

Now we can run our javascript programs. First, let’s run our query.js program to return a listing of all the cars on the ledger. A function that will query all the cars, queryAllCars, is pre-loaded in the app, so we can simply run the program as is:

node query.js

It should return something like this:

Query result count =  1
Response is  [{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}},
{"Key":"CAR1",   "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}},
{"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},
{"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}},
{"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}},
{"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}},
{"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}},
{"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}},
{"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}},
{"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]

These are the 10 cars. A black Tesla Model S owned by Adriana, a red Ford Mustang owned by Brad, a violet Fiat Punto owned by someone named Pari, and so on. The ledger is key/value based and in our implementation the key is CAR0 through CAR9. This will become particularly important in a moment.

Now let’s see what it looks like under the hood (if you’ll forgive the pun). Use an editor (e.g. atom or visual studio) and open the query.js program.

The inital section of the application defines certain variables such as chaincode ID, channel name and network endpoints:

var options = {
      wallet_path : path.join(__dirname, './network/creds'),
      user_id: 'PeerAdmin',
      channel_id: 'mychannel',
      chaincode_id: 'fabcar',
      network_url: 'grpc://localhost:7051',

This is the chunk where we construct our query:

// queryCar - requires 1 argument, ex: args: ['CAR4'],
// queryAllCars - requires no arguments , ex: args: [''],
const request = {
   chaincodeId: options.chaincode_id,
   txId: transaction_id,
   fcn: 'queryAllCars',
   args: ['']

We define the chaincode_id variable as fabcar – allowing us to target this specific chaincode – and then call the queryAllCars function defined within that chaincode.

When we issued the node query.js command earlier, this specific function was called to query the ledger. However, this isn’t the only function that we can pass.

To take a look at the others, navigate to the chaincode subdirectory and open fabcar.go in your editor. You’ll see that we have the following functions available to call - initLedger, queryCar, queryAllCars, createCar and changeCarOwner. Let’s take a closer look at the queryAllCars function to see how it interacts with the ledger.

func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {

     startKey := "CAR0"
     endKey := "CAR999"

     resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)

The function uses the shim interface function GetStateByRange to return ledger data between the args of startKey and endKey. Those keys are defined as CAR0 and CAR999 respectively. Therefore, we could theoretically create 1,000 cars (assuming the keys are tagged properly) and a queryAllCars would reveal every one.

Below is a representation of how an app would call different functions in chaincode.

_images/RunningtheSample.png

We can see our queryAllCars function up there, as well as one called createCar that will allow us to update the ledger and ultimately append a new block to the chain. But first, let’s do another query.

Go back to the query.js program and edit the constructor request to query a specific car. We’ll do this by changing the function from queryAllCars to queryCar and passing a specific “Key” to the args parameter. Let’s use CAR4 here. So our edited query.js program should now contain the following:

const request = {
      chaincodeId: options.chaincode_id,
      txId: transaction_id,
      fcn: 'queryCar',
      args: ['CAR4']

Save the program and navigate back to your fabcar directory. Now run the program again:

node query.js

You should see the following:

{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}

So we’ve gone from querying all cars to querying just one, Adriana’s black Tesla Model S. Using the queryCar function, we can query against any key (e.g. CAR0) and get whatever make, model, color, and owner correspond to that car.

Great. Now you should be comfortable with the basic query functions in the chaincode, and the handful of parameters in the query program. Time to update the ledger…

Updating the Ledger

Now that we’ve done a few ledger queries and added a bit of code, we’re ready to update the ledger. There are a lot of potential updates we could make, but let’s just create a new car for starters.

Ledger updates start with an application generating a transaction proposal. Just like query, a request is constructed to identify the channel ID, function, and specific smart contract to target for the transaction. The program then calls the channel.SendTransactionProposal API to send the transaction proposal to the peer(s) for endorsement.

The network (i.e. endorsing peer) returns a proposal response, which the application uses to build and sign a transaction request. This request is sent to the ordering service by calling the channel.sendTransaction API. The ordering service will bundle the transaction into a block and then “deliver” the block to all peers on a channel for validation. (In our case we have only the single endorsing peer.)

Finally the application uses the eh.setPeerAddr API to connect to the peer’s event listener port, and calls eh.registerTxEvent to register events associated with a specific transaction ID. This API allows the application to know the fate of a transaction (i.e. successfully committed or unsuccessful). Think of it as a notification mechanism.

Note

We don’t go into depth here on a transaction’s lifecycle. Consult the Transaction Flow documentation for lower level details on how a transaction is ultimately committed to the ledger.

The goal with our initial invoke is to simply create a new asset (car in this case). We have a separate javascript program - invoke.js - that we will use for these transactions. Just like query, use an editor to open the program and navigate to the codeblock where we construct our invocation:

// createCar - requires 5 args, ex: args: ['CAR11', 'Honda', 'Accord', 'Black', 'Tom'],
// changeCarOwner - requires 2 args , ex: args: ['CAR10', 'Barry'],
// send proposal to endorser
var request = {
    targets: targets,
    chaincodeId: options.chaincode_id,
    fcn: '',
    args: [''],
    chainId: options.channel_id,
    txId: tx_id

You’ll see that we can call one of two functions - createCar or changeCarOwner. Let’s create a red Chevy Volt and give it to an owner named Nick. We’re up to CAR9 on our ledger, so we’ll use CAR10 as the identifying key here. The updated codeblock should look like this:

var request = {
    targets: targets,
    chaincodeId: options.chaincode_id,
    fcn: 'createCar',
    args: ['CAR10', 'Chevy', 'Volt', 'Red', 'Nick'],
    chainId: options.channel_id,
    txId: tx_id

Save it and run the program:

node invoke.js

There will be some output in the terminal about Proposal Response and Transaction ID. However, all we’re concerned with is this message:

The transaction has been committed on peer localhost:7053

The peer emits this event notification, and our application receives it thanks to our eh.registerTxEvent API. So now if we go back to our query.js program and call the queryCar function against an arg of CAR10, we should see the following:

Response is  {"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"}

Finally, let’s call our last function - changeCarOwner. Nick is feeling generous and he wants to give his Chevy Volt to a man named Barry. So, we simply edit invoke.js to reflect the following:

var request = {
   targets: targets,
   chaincodeId: options.chaincode_id,
   fcn: 'changeCarOwner',
   args: ['CAR10', 'Barry'],
   chainId: options.channel_id,
   txId: tx_id

Execute the program again - node invoke.js - and then run the query app one final time. We are still querying against CAR10, so we should see:

Response is  {"colour":"Red","make":"Chevy","model":"Volt","owner":"Barry"}

Additional Resources

The Hyperledger Fabric Node SDK repo is an excellent resource for deeper documentation and sample code. You can also consult the Hyperledger Fabric community and component experts on Hyperledger Rocket Chat.