cckit

module
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 27, 2019 License: MIT

README

Hyperledger Fabric chaincode kit (CCKit)

Go Report Card Build Coverage Status

CCkit is a programming toolkit for developing and testing hyperledger fabric chaincode

Overview

A smart contract is code – invoked by a client application external to the blockchain network – that manages access and modifications to a set of key-value pairs in the World State. In Hyperledger Fabric, smart contracts are referred to as chaincode.

CCKit features
Problems with existing chaincode examples

There are several chaincode examples available:

Main problems:

  • Chaincode methods routing appeared only in HLF 1.4 and only in Node.Js chaincode
  • Lots of code duplication (json marshalling / unmarshalling, validation, access control etc)
  • Uncompleted testing tools (MockStub)
Publications

Example based on CCKit

Chaincode "Cars"

Car registration chaincode use simple golang structure with json marshalling. Example with protobuf model is here.

source code, tests

// Simple CRUD chaincode for store information about cars
package main

import (
	"errors"
	"time"

	"github.com/s7techlab/cckit/extensions/owner"
	"github.com/s7techlab/cckit/router"
	p "github.com/s7techlab/cckit/router/param"
)

var (
	ErrCarAlreadyExists = errors.New(`car already exists`)
)

const CarEntity = `CAR`
const CarRegisteredEvent = `CAR_REGISTERED`

// CarPayload chaincode method argument
type CarPayload struct {
	Id    string
	Title string
	Owner string
}

// Car struct for chaincode state
type Car struct {
	Id    string
	Title string
	Owner string

	UpdatedAt time.Time // set by chaincode method
}

// Key for car entry in chaincode state
func (c Car) Key() ([]string, error) {
	return []string{CarEntity, c.Id}, nil
}

func New() *router.Chaincode {
	r := router.New(`cars`) // also initialized logger with "cars" prefix

	r.Init(invokeInit)

	r.Group(`car`).
		// everyone  can view information about registered cars.
		Query(`List`, queryCars).                                             // chain code method name is carList
		Query(`Get`, queryCar, p.String(`id`)).                               // chain code method name is carGet, method has 1 string argument "id"
		
		Invoke(`Register`, invokeCarRegister, p.Struct(`car`, &CarPayload{}), // 1 struct argument
			owner.Only) // allow access to method only for chaincode owner (authority)
			            // Only authority can register car information

	return router.NewChaincode(r)
}

// ======= Init ==================
func invokeInit(c router.Context) (interface{}, error) {
	return owner.SetFromCreator(c)
}

// ======= Chaincode methods =====

// car get info chaincode method handler
func queryCar(c router.Context) (interface{}, error) {
	// get state entry by composite key using CarKeyPrefix and car.Id
	//  and unmarshal from []byte to Car struct
	return c.State().Get(&Car{Id: c.ParamString(`id`)})
}

// cars car list chaincode method handler
func queryCars(c router.Context) (interface{}, error) {
	return c.State().List(
		CarEntity, // get list of state entries of type CarKeyPrefix
		&Car{})    // unmarshal from []byte and append to []Car slice
}

// carRegister car register chaincode method handler
func invokeCarRegister(c router.Context) (interface{}, error) {
	// arg name defined in router method definition
	p := c.Param(`car`).(CarPayload)

	t, _ := c.Time() // tx time
	car := &Car{     // data for chaincode state
		Id:        p.Id,
		Title:     p.Title,
		Owner:     p.Owner,
		UpdatedAt: t,
	}

	// trigger event
	c.Event().Set(CarRegisteredEvent, car)

	return car, // peer.Response payload will be json serialized car data
		//put json serialized data to state
		// create composite key using CarKeyPrefix and car.Id
		c.State().Insert(car)
}
Test for chaincode

Tests are based on a modified MockStub

package main

import (
	"testing"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	examplecert "github.com/s7techlab/cckit/examples/cert"
	"github.com/s7techlab/cckit/extensions/owner"
	testcc "github.com/s7techlab/cckit/testing"
	expectcc "github.com/s7techlab/cckit/testing/expect"
)

func TestCars(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Cars Suite")
}

var _ = Describe(`Cars`, func() {

	//Create chaincode mock
	cc := testcc.NewMockStub(`cars`, New())

	// load actor certificates
	actors, err := examplecert.Actors(map[string]string{
		`authority`: `s7techlab.pem`,
		`someone`:   `victor-nosov.pem`,
	})
	if err != nil {
		panic(err)
	}

	// cars fixtures
	car1 := &Car{
		Id:    `A777MP77`,
		Title: `BMW`,
		Owner: `victor-nosov`,
	}

	car2 := &Car{
		Id:    `O888OO77`,
		Title: `TOYOTA`,
		Owner: `alexander`,
	}

	BeforeSuite(func() {
		// init chaincode
		expectcc.ResponseOk(cc.From(actors[`authority`]).Init()) // init chaincode from authority
	})

	Describe("Car", func() {

		It("Allow authority to add information about car", func() {
			//invoke chaincode method from authority actor
			expectcc.ResponseOk(cc.From(actors[`authority`]).Invoke(`carRegister`, car1))
		})

		It("Disallow non authority to add information about car", func() {
			//invoke chaincode method from non authority actor
			expectcc.ResponseError(
				cc.From(actors[`someone`]).Invoke(`carRegister`, car1),
				owner.ErrOwnerOnly) // expect "only owner" error
		})

		It("Disallow authority to add duplicate information about car", func() {
			expectcc.ResponseError(
				cc.From(actors[`authority`]).Invoke(`carRegister`, car1),
				ErrCarAlreadyExists) //expect already exists
		})

		It("Allow everyone to retrieve car information", func() {
			car := expectcc.PayloadIs(cc.Invoke(`carGet`, car1.Id),
				&Car{}).(Car)

			Expect(car.Title).To(Equal(car1.Title))
			Expect(car.Id).To(Equal(car1.Id))
		})

		It("Allow everyone to get car list", func() {
			//  &[]Car{} - declares target type for unmarshalling from []byte received from chaincode
			cars := expectcc.PayloadIs(cc.Invoke(`carList`), &[]Car{}).([]Car)

			Expect(len(cars)).To(Equal(1))
			Expect(cars[0].Id).To(Equal(car1.Id))
		})

		It("Allow authority to add more information about car", func() {
			// register second car
			expectcc.ResponseOk(cc.Invoke(`carRegister`, car2))
			cars := expectcc.PayloadIs(
				cc.From(actors[`authority`]).Invoke(`carList`),
				&[]Car{}).([]Car)

			Expect(len(cars)).To(Equal(2))
		})
	})
})

Directories

Path Synopsis
Package convert for transforming between json serialized []byte and go structs
Package convert for transforming between json serialized []byte and go structs
examples
cars
Simple CRUD chaincode for store information about cars Simple CRUD chaincode for store information about cars
Simple CRUD chaincode for store information about cars Simple CRUD chaincode for store information about cars
cpaper/schema
Package schema is a generated protocol buffer package.
Package schema is a generated protocol buffer package.
payment/schema
Package schema is a generated protocol buffer package.
Package schema is a generated protocol buffer package.
extensions
owner
Package owner provides method for storing in chaincode state information about chaincode owner
Package owner provides method for storing in chaincode state information about chaincode owner
pinger
Package pinger contains structure and functions for checking chain code accessibility
Package pinger contains structure and functions for checking chain code accessibility
Package access contains structs for storing chaincode access control information
Package access contains structs for storing chaincode access control information
Package router provides base router for using in chaincode Invoke function
Package router provides base router for using in chaincode Invoke function

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL