Creating and Deploying a Custom Chain Code for Hyper ledger Fabric
In this tutorial we are going to learn to create a custom chain code (smart contract). The chain code is based on the fabcar chain code in the hyper ledger official tutorial. I have reverse engineered it to work as a primitive food regulation chain code.
First we will take a look at the chain code. We will do it part by part.
import ("bytes""encoding/json""fmt""strconv""github.com/hyperledger/fabric/core/chaincode/shim"sc "github.com/hyperledger/fabric/protos/peer")
This part consists of necessary imports for the go chain code.
Then we make a structure for the chain code.
type SmartContract struct {}// Define the stock structure, with 4 properties. Structure tags are used by encoding/json librarytype Stock struct {Type string `json:"type"`Quantity string `json:"quantity"`Importer string `json:"importer"`Retailer string `json:"retailer"`}
We made a struct called stock which contains the necessary data set needed for the smart contract.
Now we take about the instantiation and invoke part. Chain code needs to be instantiated to work in the peers.
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {return shim.Success(nil)}func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {// Retrieve the requested Smart Contract function and argumentsfunction, args := APIstub.GetFunctionAndParameters()// Route to the appropriate handler function to interact with the ledger appropriatelyif function == "queryStock" {return s.queryStock(APIstub, args)} else if function == "initLedger" {return s.initLedger(APIstub)} else if function == "createStock" {return s.createStock(APIstub, args)} else if function == "queryAllStocks" {return s.queryAllStocks(APIstub)} else if function == "assignStock" {return s.assignStock(APIstub, args)}return shim.Error("Invalid Smart Contract function name.")}
In the invoke part we call the various functions of the chain code to work.
Now for the rest of the chain code.
func (s *SmartContract) queryStock(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {if len(args) != 1 {return shim.Error("Incorrect number of arguments. Expecting 1")}carAsBytes, _ := APIstub.GetState(args[0])return shim.Success(carAsBytes)}func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {stocks := []Stock{Stock{Type: "CowMeat", Quantity: "205kg", Importer: "BengolMeat", Retailer: "BengolMeat"},Stock{Type: "GoatMeat", Quantity: "100kg", Importer: "RoyalMeat", Retailer: "MenaBajar"},Stock{Type: "ChickenMeat", Quantity: "25kg", Importer: "GovtMeat", Retailer: "Shopno"},Stock{Type: "KhasirMeat", Quantity: "655kg", Importer: "AiubMeat", Retailer: "AgoraMeat"},Stock{Type: "GoruMeat", Quantity: "2264kg", Importer: "BeyondlMeat", Retailer: "MeatMarket"},}i := 0for i < len(stocks) {fmt.Println("i is ", i)stockAsBytes, _ := json.Marshal(stocks[i])APIstub.PutState("STOCK"+strconv.Itoa(i), stockAsBytes)fmt.Println("Added", stocks[i])i = i + 1}return shim.Success(nil)}func (s *SmartContract) createStock(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {if len(args) != 5 {return shim.Error("Incorrect number of arguments. Expecting 5")}var stock = Stock{Type: args[1], Quantity: args[2], Importer: args[3], Retailer: args[4]}stockAsBytes, _ := json.Marshal(stock)APIstub.PutState(args[0], stockAsBytes)return shim.Success(nil)}func (s *SmartContract) queryAllStocks(APIstub shim.ChaincodeStubInterface) sc.Response {startKey := "STOCK0"endKey := "STOCK999"resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)if err != nil {return shim.Error(err.Error())}defer resultsIterator.Close()// buffer is a JSON array containing QueryResultsvar buffer bytes.Bufferbuffer.WriteString("[")bArrayMemberAlreadyWritten := falsefor resultsIterator.HasNext() {queryResponse, err := resultsIterator.Next()if err != nil {return shim.Error(err.Error())}// Add a comma before array members, suppress it for the first array memberif 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-isbuffer.WriteString(string(queryResponse.Value))buffer.WriteString("}")bArrayMemberAlreadyWritten = true}buffer.WriteString("]")fmt.Printf("- queryAllStocks:\n%s\n", buffer.String())return shim.Success(buffer.Bytes())}func (s *SmartContract) assignStock(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {if len(args) != 2 {return shim.Error("Incorrect number of arguments. Expecting 2")}stockAsBytes, _ := APIstub.GetState(args[0])stock := Stock{}json.Unmarshal(stockAsBytes, &stock)stock.Retailer = args[1]stockAsBytes, _ = json.Marshal(stock)APIstub.PutState(args[0], stockAsBytes)return shim.Success(nil)}func main() {// Create a new Smart Contracterr := shim.Start(new(SmartContract))if err != nil {fmt.Printf("Error creating new Smart Contract: %s", err)}}
This portion of the code deals with functions in the chain code. The first function is queryStock, it takes one argument key of the stock, and it gives you details. The next function is initLedger , it populates the ledger with some data to query and do other stuffs with it. QueryAllStocks name is definition enough. assignStock is used to assign a stock to a different retailer. The main function is for unit testing only.
Now the final code:
Now we must compile it first. For compiling go chain code. We must keep the file in the go path. Go path setting can be learned from this link:
The we will build the code using the command.
go build
We will have a compiled go file now.
The compiled go file and the go file then be placed in the path specefied in the compose-cli file.
The file is also given here for better understanding. If you want to know what is docker-compose-cli and what is it necessary for it can be found here.
I have used another network in this tutorial but the concept is same.
In this network we have 3 organization and each organization have one peer. The volume part where we specified for the chain code is not absolute. It’s a relative path , it relative to the gopath.
We will install the chain code in the peers following this procedure. After starting the network and joining the peers to a channel. More data for this can be found here:
Again the network is different but the concept is same.Remember all the command must be issued while you are inside the network folder.
We will enter each following this procedure:
The general command structure for this is:
For Customer Organization peer 0 the command is:
docker exec -e "CORE_PEER_LOCALMSPID=CustomerMSP" -e "CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/fabric-samples/food-network/crypto-config/peerOrganizations/customer.food-network.com/peers/peer0.customer.food-network.com/tls/ca.crt" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/fabric-samples/food-network/crypto-config/peerOrganizations/customer.food-network.com/users/Admin@customer.food-network.com/msp" -e "CORE_PEER_ADDRESS=peer0.customer.food-network.com:7051" -it cli bash
After entering the peer we will first import certificate file.
export ORDERER_CA=/opt/gopath/fabric-samples/food-network/crypto-config/ordererOrganizations/food-network.com/orderers/orderer.food-network.com/msp/tlscacerts/tlsca.food-network.com-cert.pem
The channel name here is: channelshovon
Now we will install chain in peer 0 of customer org. The chain code is inside the food code folder in the chain folder.
CORE_PEER_ADDRESS=peer0.customer.food-network.com:7051 peer chaincode install -n foodcode -p chain/foodcode -v 1.0
The generic command for this is:
CORE_PEER_ADDRESS=****peer address****peer chaincode install -n **chaincodename**-p chain/**location of the chaincode folder** -v **version for the chaincode**
Now we can check the list of installed chain codes using this commadn.
peer chaincode list --installed
Output:
We can see now the chain code is installed.Now we must instantiate it for further use.
Command:
peer chaincode instantiate -o orderer.food-network.com:7050 -C channelshovon -n foodcode -v 1.0 -l golang -c '{"Args":["init"]}' -P "OR('CustomerMSP.peer', 'ImporterMSP.peer','DistributorMSP.peer')" --tls true --cafile $ORDERER_CA
The output will be like this:
Now we can use functions in the chain code. First we will initLedger for further use. Then we will use queryAllStocks,createStock,queryStock,assignStock and see their uses and output.
peer chaincode invoke -o orderer.food-network.com:7050 --tls --cafile $ORDERER_CA -C channelshovon -n foodcode -c '{"Args":["initLedger"]}peer chaincode invoke -o orderer.food-network.com:7050 --tls --cafile $ORDERER_CA -C channelshovon -n foodcode -c '{"Args":["queryAllStocks"]}'peer chaincode invoke -o orderer.food-network.com:7050 --tls --cafile $ORDERER_CA -C channelshovon -n foodcode -c '{"Args":["createStock", "STOCK7","DuckMeat", "290ton", "BeMt", "Ao"]}'peer chaincode invoke -o orderer.food-network.com:7050 --tls --cafile $ORDERER_CA -C channelshovon -n foodcode -c '{"Args":["queryStock","STOCK5"]}peer chaincode invoke -o orderer.food-network.com:7050 --tls --cafile $ORDERER_CA -C channelshovon -n foodcode -c '{"Args":["assignStock","STOCK5","ACI"]}'
The outputs can be seen in the images.
The chain creates a shared ledgers between connected peers.
This is way a chain code can be deployed in a hyper ledger fabric network.