How to write chaincode and invoke chaincode functions

In previous articles we talked about:

In this article we will learn how to write chaincode using Go programming language.

What is chaincode?

Chaincode is the place where we define logic (functions) of our application. Chaincode needs to be installed on every peer that will interact with the ledger. It also needs to be instantiated on the channel. After that, end users can invoke chaincode functions through a client-side application that interfaces with a network peer.

Chaincode is the program written in Go, node.js or Java that implements a prescribed interface.

How to write chaincode?

To write a chaincode in Golang, we will use shim package. It provides APIs for the chaincode to access its state variables, transaction context and call other chaincodes.

Let’s create folder structure and file in which we will write our chaincode. Folder structure looks like in the picture below.

Folder strucure for chaincode in CryptoKajmak application

Navigate to the crypto-kajmak folder created in the previous article:

$ cd crypto-kajmak

Create necessary new folders and the file kajmak-chaincode.go:

$ mkdir chaincode
$ cd chaincode
$ mkdir kajmak-app
$ cd kajmak-app
$ touch kajmak-chaincode.go

kajmak-chaincode.go will contain all the chaincode code listed below.

First, we will import necessary packages:

import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
sc "github.com/hyperledger/fabric/protos/peer"
)
  • Package bytes implements functions for the manipulation of byte slices. It is analogous to the facilities of the strings package.
  • Package json implements encoding and decoding of JSON as defined in RFC 7159. The mapping between JSON and Go values is achieved by using the Marshal and Unmarshal functions.
  • Package fmt implements formatted I/O with functions analogous to C’s printf and scanf. The format ‘verbs’ are derived from C’s but are simpler.
  • Package strconv implements conversions to and from string representations of basic data types.

Next, we implement SmartContract structure like this:

type SmartContract struct {}

This is the chaincode interface that must be implemented by all chaincodes. We will use it in main function to start the chaincode in the container during instantiation.

In the chaincode, we define assets that will be stored in the ledger. In our case, the asset is kajmak:

//Kajmak struct definition
type Kajmak struct {
Name string `json:"name"`
Owner string `json:"owner"`
Animal string `json:"animal"`
Location string `json:"location"`
Quantity string `json:"quantity"`
ProductionDate string `json:"production_date"`
ExpirationDate string `json:"expiration_date"`
}

Every chaincode must have implemented two functions: Init and Invoke.

Init function is called during the chaincode instantiation and the purpose is to prepare ledger for the future requests. In the CryptoKajmak application, we will return nil if the call of the function was successful.

//Init method definition
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
return shim.Success(nil)
}

Invoke function is called for all future requests from the client application towards the blockchain network. Here we define all our custom functions or what we want for our application to do (for example, to read asset from the ledger, to write asset in the ledger, to update asset, to delete asset).

The CryptoKajmak application has this functions which make the logic of the application or represent what the application can do:

initLedgerFirst — adds kajmak with ID 1 into the ledger. The purpose of this function is to start chaincode docker container for Org1 after the instantiation so the end user does not have to wait later when he begins to use the application (process of starting container takes some time).

initLedgerSecond — adds kajmak with ID 2 into the ledger. The purpose of this function is to start chaincode docker container for Org2 after the instantiation so the end user does not have to wait later when he begins to use the application (process of starting container takes some time).

recordKajmak — records new kajmak into the ledger.

queryAllKajmak — reads all kajmak from the ledger.

changeKajmakOwner — updates owner attribut of the kajmak when the kajmak changes its owner.

deleteKajmak — deletes kajmak from the ledger.

changeKajmakQuantity — updates quantity attribut of the kajmak in the process of mixing two kajmaks.

//Invoke method definition
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
function, args := APIstub.GetFunctionAndParameters()
if function == "initLedgerFirst" {
return s.initLedgerFirst(APIstub)
} else if function == "initLedgerSecond" {
return s.initLedgerSecond(APIstub)
} else if function == "recordKajmak" {
return s.recordKajmak(APIstub, args)
} else if function == "queryAllKajmak" {
return s.queryAllKajmak(APIstub)
} else if function == "changeKajmakOwner" {
return s.changeKajmakOwner(APIstub, args)
} else if function == "deleteKajmak" {
return s.deleteKajmak(APIstub, args)
} else if function == "changeKajmakQuantity" {
return s.changeKajmakQuantity(APIstub, args)
}
return shim.Error("Invalid Smart Contract function name.")
}

Implementation of the initLedgerFirst function:

//initLedgerFirst method deifinition
func (s *SmartContract) initLedgerFirst(APIstub shim.ChaincodeStubInterface) sc.Response {
var kajmak = Kajmak{Name: "Kajmak1", Owner: "majra", Animal: "Sheep", Location: "Vlasic", Quantity: "340", ProductionDate: "05.10.2018. 10:55 am", ExpirationDate: "15.10.2019. 10:55 am"}
kajmakAsBytes, _ := json.Marshal(kajmak)
APIstub.PutState(strconv.Itoa(1), kajmakAsBytes)
fmt.Println("Added", kajmak)
return shim.Success(nil)
}

Implementation of the initLedgerSecond function:

//initLedgerSecond method deifinition
func (s *SmartContract) initLedgerSecond(APIstub shim.ChaincodeStubInterface) sc.Response {
var kajmak = Kajmak{Name: "Kajmak2", Owner: "daca", Animal: "Cow", Location: "Trebinje", Quantity: "540", ProductionDate: "06.10.2018. 10:56 pm", ExpirationDate: "16.10.2019. 10:56 pm" }
kajmakAsBytes, _ := json.Marshal(kajmak)
APIstub.PutState(strconv.Itoa(2), kajmakAsBytes)
fmt.Println("Added", kajmak)
return shim.Success(nil)
}

Implementation of the queryAllKajmak function:

//queryAllKajmak method definition
func (s *SmartContract) queryAllKajmak(APIstub shim.ChaincodeStubInterface) sc.Response {
startKey := "0"
endKey := "999"
resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close()
//buffer is a JSON array containing QueryResults
var buffer bytes.Buffer
buffer.WriteString("[")
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
// Add comma before array members,suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"Key\":")
buffer.WriteString("\"")
buffer.WriteString(queryResponse.Key)
buffer.WriteString("\"")
buffer.WriteString(", \"Record\":")
//Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")
fmt.Printf("- queryAllKajmak:\n%s\n", buffer.String())
return shim.Success(buffer.Bytes())
}

Implementation of the recordKajmak function:

//recordKajmak method definition
func (s *SmartContract) recordKajmak(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 8 {
return shim.Error("Incorrect number of arguments. Expecting 8")
}
var kajmak = Kajmak{ Name: args[1], Owner: args[2], Animal: args[3], Location: args[4], Quantity: args[5], ProductionDate: args[6], ExpirationDate: args[7] }
kajmakAsBytes, _ := json.Marshal(kajmak)
err := APIstub.PutState(args[0], kajmakAsBytes)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to record tuna catch: %s", args[0]))
}
fmt.Printf("- recordKajmak:\n%s\n", kajmak)
return shim.Success(nil)
}

Implementation of the changeKajmakOwner function:

//changeKajmakOwner method definition
func (s *SmartContract) changeKajmakOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
kajmakAsBytes, _ := APIstub.GetState(args[0])
if kajmakAsBytes == nil {
return shim.Error("Could not locate kajmak")
}
kajmak := Kajmak{}
json.Unmarshal(kajmakAsBytes, &kajmak)
kajmak.Owner = args[1]
kajmakAsBytes, _ = json.Marshal(kajmak)
err := APIstub.PutState(args[0], kajmakAsBytes)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to change kajmak owner: %s", args[0]))
}
fmt.Printf("-changeKajmakOwner:\n")
return shim.Success(nil)
}

Implementation of the changeKajmakQuantity function:

//changeKajmakQuantity method definition
func (s *SmartContract) changeKajmakQuantity(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
kajmakAsBytes, _ := APIstub.GetState(args[0])
if kajmakAsBytes == nil {
return shim.Error("Could not locate kajmak")
}
kajmak := Kajmak{}
json.Unmarshal(kajmakAsBytes, &kajmak)
kajmak.Quantity = args[1]
kajmakAsBytes, _ = json.Marshal(kajmak)
err := APIstub.PutState(args[0], kajmakAsBytes)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to change kajmak quantity: %s", args[0]))
}
fmt.Printf("- changeKajmakQuantity:\n")
return shim.Success(nil)
}

Implementation of the deleteKajmak function:

//deleteKajmak method definition
func (s *SmartContract) deleteKajmak(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) != 8 {
return shim.Error("Incorrect number of arguments. Expecting 8")
}
err := APIstub.DelState(args[0])
if (err != nil) {
return shim.Error(fmt.Sprintf("Failed to delete kajmak: %s", args[0]))
}
eventPayload := "Kajmak with ID " + args[0] + " whose owner is " + args[2] + " was deleted because it has expired on date " + args[7]
payloadAsBytes := []byte(eventPayload)
eventErr := APIstub.SetEvent("deleteEvent",payloadAsBytes)
if (eventErr != nil) {
return shim.Error(fmt.Sprintf("Failed to emit event"))
}
fmt.Printf("- deleteKajmak:\n%s\n", args[0])
return shim.Success(nil);
}

In the above functions, we used this APIs to access the ledger:

getFunctionAndParameters

GetFunctionAndParameters returns the first argument as the function name and the rest of the arguments as parameters in a string array. Only use GetFunctionAndParameters if the client passes arguments intended to be used as strings.

GetState

GetState returns the value of the specified `key` from the ledger. Note that GetState doesn’t read data from the writeset, which has not been committed to the ledger. In other words, GetState does not consider data modified by PutState that has not been committed. If the key does not exist in the state database, (nil, nil) is returned.

PutState

PutState puts the specified `key` and `value` into the transaction’s writeset as a data-write proposal. PutState does not effect the ledger until the transaction is validated and successfully committed. Simple keys must not be an empty string and must not start with null character (0x00), in order to avoid range query collisions with composite keys, which internally get prefixed with 0x00 as composite key namespace.

DelState

DelState records the specified `key` to be deleted in the writeset of the transaction proposal. The `key` and its value will be deleted from the ledger when the transaction is validated and successfully committed.

At the end of the chaincode, we implement main function that starts the chaincode in the container during instantiation.

*
/*
* main function *
calls the Start function
The main function starts the chaincode in the container during instantiation.
*/
func main() {
// Create a new Smart Contract
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error creating new Smart Contract: %s", err)
}
}

We will write startKajmak.sh script that will help us to:

  • install node modules
  • set chaincode path
  • start docker containers
  • install chaincode on peers
  • instantiate chaincode on the channel
  • invoke initLedgerFirst and initLedgerSecond functions

startKajmak.sh script:

#!/bin/bash
set -e
starttime=$(date +%s)
function installNodeModules() {
echo
if [ -d node_modules ]; then
echo "============== node modules installed already ============="
else
echo "============== Installing node modules ============="
npm install
fi
echo
}
installNodeModules# Language defaults to "golang"
LANGUAGE="golang"
##set chaincode path
function setChaincodePath(){
LANGUAGE=`echo "$LANGUAGE" | tr '[:upper:]' '[:lower:]'`
case "$LANGUAGE" in
"golang")
CC_SRC_PATH="$PWD/chaincode/kajmak-app"
;;
*) printf "\n ------ Language $LANGUAGE is not supported yet ------\n"$
exit 1
esac
}
setChaincodePath//start the docker containers
cd kajmak-network
./start.sh
//start the cli container
docker-compose -f ./docker-compose.yaml up -d cli
//install chaincode on peer0 from org1
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode install -n kajmak-app -v 1.0 -p github.com/kajmak-app
//install chaincode on peer0 from org2
docker exec -e "CORE_PEER_LOCALMSPID=Org2MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp" -e "CORE_PEER_ADDRESS=peer0.org2.example.com:7051" cli peer chaincode install -n kajmak-app -v 1.0 -p github.com/kajmak-app
//instantiate chaincode on the channel
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n kajmak-app -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
sleep 10//invoke initLedgerFirst and initLedgerSecond functions
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n kajmak-app -c '{"function":"initLedgerFirst","Args":[""]}'
docker exec -e "CORE_PEER_LOCALMSPID=Org2MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp" -e "CORE_PEER_ADDRESS=peer0.org2.example.com:7051" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n kajmak-app -c '{"function":"initLedgerSecond","Args":[""]}'#printf "\nTotal execution time : $(($(date +%s) - starttime)) secs ...\n\n"printf "\nBlockchain network is up and running! \n"

We provided for you the github repository that includes all files written so far. Actually, the repository includes final version and represents fully developed CryptoKajmak blockchain web application including front-end written in AngularJS and back-end written in Node.js.

More details about relationship between folders and files can be found in README file on the repository.

Note: crypto-kajmak folder on the github repository includes concepts that we will talk about in future articles (such as events and registration and enrollment of new users into the network using Certificate Authority). Also, some files that we already created may look slightly different and that is because we will change them in the future articles.

We hope to see you in the next article where we will talk about chaincode events.

--

--