Writing the Smart Contract

Fabric calls a smart contract as "chaincode".

Programming Language: Go

Fabric allows a smart contract to be written in Javascript and Go. In this tutorial, we will implement the smart contract in Go.

Write the smart contract

Open a terminal, ssh to node-1

ssh node-1.cse.cuhk.edu.hk

Create a working directory for the source code of the smart contract

mkdir ~/fabric_lab_code
cd ~/fabric_lab_code

Create a file main.go in the working directory

vim main.go

Copy the following code into main.go. The smart contract has 3 functions: Issue, Report, and Refund.

package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"github.com/hyperledger/fabric-contract-api-go/contractapi"
)

// Define a struct of a insurance
type Insurance struct {
	InsuranceNo string
	Issuer string
	IsReported bool
	IsRefund bool
}

// Define a struct of a Item
type Item struct {
	Owner string
	itemSerialNo string
	Insurances map[string]Insurance
	IsRefund bool
}

// Define a struct for smart contract which inhert super class
type InsuranceContract struct {
	contractapi.Contract
}

// Utility function to serialize `Item` into byte such that we can persist it
// into the statedb
func Serializeitem(item Item) ([]byte, error) {
	b := bytes.Buffer{}
	e := gob.NewEncoder(&b)
	err := e.Encode(item)
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}

// Utility function to deserialize `Item` from byte such taht we can load it
// from the statedb adn ledger
func Deserializeitem(by []byte) (Item, error) {
	m := Item{}
	b := bytes.Buffer{}
	b.Write(by)
	d := gob.NewDecoder(&b)
	err := d.Decode(&m)
	if err != nil {
		return m, err
	}
	return m, nil
}

func main() {

	//gob is Go library for (de)-serialization
	gob.Register(Item{})
	gob.Register(Insurance{})

	chaincode, err := contractapi.NewChaincode(new(InsuranceContract))
	if err != nil {
		fmt.Printf("Error starting Insurance: %s", err)
	}
	//start this contract and wait for others to invoke its function (e.g., Issue)
	if err := chaincode.Start(); err != nil {
		fmt.Printf("Error starting Insurance: %s", err.Error())
	}
}

//for others to invoke to create a new insurance for a Item
func (t *InsuranceContract) Issue(ctx contractapi.TransactionContextInterface, owner, issuer, itemSerialNo, insuranceNo string) error {
	var item Item

	//access the smart contract's key-value store by GetState
	serializeditem, err := ctx.GetStub().GetState(itemSerialNo)
	if err != nil {
		return fmt.Errorf("Can't read Item %s from statedb\n", itemSerialNo)
	}

	//Get Item object from ledger, otherwise create one
	if serializeditem != nil{
		item, err = Deserializeitem(serializeditem)

		if err != nil{
			return fmt.Errorf("Fail to deserialize Item %s\n", itemSerialNo)
		}

		//Do some checking
		if item.Owner != owner{
			return fmt.Errorf("Item %s does not belong to %s\n", itemSerialNo, owner)
		}
		if item.IsRefund{
			return fmt.Errorf("Item %s has already been refunded\n", itemSerialNo)
		}
	}else{
		item = Item{
			owner,
			itemSerialNo,
			make(map[string]Insurance),
			false,
		}
	}

	//Attach insurance
	insurance := Insurance {
		insuranceNo,
		issuer,
		false,
		false,
	}

	item.Insurances[insuranceNo] = insurance

	serializeditem, err = Serializeitem(item)

	if err != nil{
		return fmt.Errorf("Fail to serialize Item %s\n", itemSerialNo)
	}

	//Put to the smart contract's key-value store
	err = ctx.GetStub().PutState(itemSerialNo, serializeditem)

	if err != nil{
		return fmt.Errorf("Fail to persist Item %s\n", itemSerialNo)
	}
	return nil
}

func (t *InsuranceContract) Report(ctx contractapi.TransactionContextInterface, itemSerialNo, insuranceNo string) error {
	serializeditem, err := ctx.GetStub().GetState(itemSerialNo)
	if err != nil {
		return fmt.Errorf("Item %s doesn't exist\n", itemSerialNo)
	}

	item, err := Deserializeitem(serializeditem)
	if err != nil {
		return fmt.Errorf("Fail to deserialize Item %s\n", serializeditem)
	}

	//Do some checking
	if item.IsRefund{
		return fmt.Errorf("Item %s has already been refunded\n", itemSerialNo)
	}

	insurance, exist := item.Insurances[insuranceNo]
	if !exist {
		return fmt.Errorf("Insurance %s doesn't exist\n", insuranceNo)
	}

	insurance.IsReported = true
	item.Insurances[insuranceNo] = insurance

	serializeditem, err = Serializeitem(item)

	if err != nil{
		return fmt.Errorf("Fail to serialize Item %s\n", itemSerialNo)
	}
	err = ctx.GetStub().PutState(itemSerialNo, serializeditem)

	if err != nil{
		return fmt.Errorf("Fail to persist Item %s\n", itemSerialNo)
	}
	return nil
}

func (t *InsuranceContract) Refund(ctx contractapi.TransactionContextInterface, itemSerialNo, insuranceNo string) error {
	serializeditem, err := ctx.GetStub().GetState(itemSerialNo)
	if err != nil {
		return fmt.Errorf("Item %s doesn't exist\n", itemSerialNo)
	}

	item, err := Deserializeitem(serializeditem)
	if err != nil {
		return fmt.Errorf("Fail to deserialize Item %s\n", serializeditem)
	}

	insurance, exist := item.Insurances[insuranceNo]
	if !exist {
		return fmt.Errorf("Insurance %s doesn't exist\n", insuranceNo)
	}

	if item.IsRefund {
		return fmt.Errorf("Item %s has already been refunded\n", itemSerialNo)
	}
	if !insurance.IsReported {
		return fmt.Errorf("Insurance %s is not reported\n", insuranceNo)
	}
	if insurance.IsRefund {
		return fmt.Errorf("Insurance %s is refunded\n", insuranceNo)
	}

	//set Item and Insurance = refunded
	item.IsRefund = true
	insurance.IsRefund = true
	item.Insurances[insuranceNo] = insurance

	serializeditem, err = Serializeitem(item)

	if err != nil {
		return fmt.Errorf("Fail to serialize Item %s\n", itemSerialNo)
	}
	err = ctx.GetStub().PutState(itemSerialNo, serializeditem)

	if err != nil {
		return fmt.Errorf("Fail to persist Item %s\n", itemSerialNo)
	}
	return nil
}

Save main.go.

Copy the following code into go.mod.

module smartcontract

go 1.15

require github.com/hyperledger/fabric-contract-api-go v1.1.1

require (
	github.com/go-openapi/jsonpointer v0.20.0 // indirect
	github.com/go-openapi/jsonreference v0.20.2 // indirect
	github.com/go-openapi/spec v0.20.9 // indirect
	github.com/gobuffalo/envy v1.10.2 // indirect
	github.com/gobuffalo/packd v1.0.2 // indirect
	github.com/hyperledger/fabric-chaincode-go v0.0.0-20230731094759-d626e9ab09b9 // indirect
	github.com/joho/godotenv v1.5.1 // indirect
	github.com/rogpeppe/go-internal v1.11.0 // indirect
	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
	golang.org/x/mod v0.14.0 // indirect
	golang.org/x/sys v0.14.0 // indirect
	golang.org/x/text v0.14.0 // indirect
	google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
	google.golang.org/grpc v1.59.0 // indirect
)

Save go.mod.

Execute the following command to tidy the Go module.

go mod tidy

Last updated