CCKit: Routing and middleware for Hyperledger Fabric Golang chaincode
There are a lot of examples and tutorials about Hyperledger Fabric chaincode development. This article describes the methods that simplify and accelerate the chaincode development through the usage of the CCKit library, containing implementation of basic chaincode building components — routing, middleware and chaincode invocation context, as well as provide chaincode testing utilities.
Chaincode components
Chaincode is a program, written in Go, Javascript or Java. Chaincode (smart contracts in the Fabric world) implements some business logic (for example, ERC20 token functionality) and interacts with the shared ledger. Chaincode must implement a prescribedChaincode interface
.
The Init
method is called when a chaincode receives an instantiate
or upgrade
transaction. Init
method may perform any necessary initialization, including setting default application state. The Invoke
method is called in response to receiving an invoke
transaction to process transaction proposals.
The another interface in the chaincode “shim” APIs is the ChaincodeStubInterface
which is used for access and modify the ledger, and to make invocations between chaincodes.
Chaincode examples
In addition to Hyperledger Fabric documentation, several chaincode examples are also available :
- Blockchain insurance app (2018)
- Marbles
- Car-lease-demo (2017)
They are all quite verbose and contain a lot of function switching logic (some kind of “routing”), argument number validation, json marshalling / unmarshalling etc
Introducing library for chaincode method routing
To simplify chaincode development, we tried to compose in single library CCKit common software development patterns, such as routing, middleware and invoke context:
Routing refers to determining how an application responds to a client request to a particular endpoint. Chaincode router uses rules about how to map chaincode invocation to particular handler, as well as what kind of middleware needs to be used during request, for example how to convert incoming argument from []byte
to target type (string
, struct
etc)
Invoke context is abstraction over ChaincodeStubInterface, represents the context of the current chaincode invocation. It holds request, response and client (Identity) reference, converted parameters, as well as state and log reference . As Context
is an interface, it is easy to extend it with custom methods.
Middleware functions are functions that have access to the invoke context, invoke result and the next middleware function in the chaincode’s invoke-response cycle. The next middleware function is commonly denoted by a variable named next
.
Middleware functions can perform the following tasks:
- Convert input args from byte slice to desired type
- Check access control requirements
- End the request-response cycle.
- Call the next middleware function in the stack.
CCKit library includes implementation of these patterns and provides:
- Centralized chaincode invocation handling
- Middleware support
- Chaincode method access control
- Automatic json marshalling / unmarshalling, entry key generation
- Testing tools (extended version of MockStub)
Simple asset chaincode — cars registration example
We will demonstrate the use of router and middleware by implementing a simple chaincode application that manages simple “assets” — cars. In this example only one authority can register cars and all can view information about registered cars
Getting started with CCKit
Before you begin, be sure to get CCkit from github.com:
git clone git@github.com:s7techlab/cckit.git
and get dependencies using dep command:
dep ensure -vendor-only
Implementing chaincode interface
As with every chaincode, it implements the Chaincode interface in particular, Init
and Invoke
functions
In our example this functions are very simple — in Init
function we put in chaincode state identifier of user, who instantiate chaincode (“owner”). In Invoke
we delegate handling of chaincode method invocation to router.
Chaincode asset
As shown in many other examples, assets can be represented as complex structures (Golang structs). The chaincode itself can store data as a string in a key/value pair setup. Thus, we will marshal struct to JSON string before putting into chaincode state and unmarshal after getting from state.
Attributes of a car:
- id (unique string, will be used as key)
- title (string)
- owner (string)
Also we create payload structure for car registration information. It contains only fields, allowed for pass to chaincode.
Defining chaincode methods
In chaincode creation func we can determine chaincode functions and theirs arguments. In this example chaincode have three functions:
- carList, returns slice of
Car
structs - carList, takes car id and returns
Car
struct - carRegister, takes
CarPayload
struct and returns car registration result. Access to this method allowed only for chaincode owner, access control is implemented usingowner.Only
middleware
Chaincode methods implementation
Chaincode methods, handled by router takesContext
as argument and returns interface{}
and error
.
func (router.Context) (interface{}, error)
Returned values will be automatically converted to Pb.response
: interface{}
will be marshalled to []byte, response status will be shim.OK
or shim.Error
depending on the error
value.
Method arguments, defined in the router, will be automatically converted from byte slice to desired type (string
or CarPayload
struct)
Chaincode function also use State
methods, which simplifies interaction with chaincode state.
All together
https://github.com/s7techlab/cckit/blob/master/examples/cars/cars.go
Chaincode main file
Finally, we need to create main package with main
function, which will call the shim.Start function.
https://github.com/s7techlab/cckit/blob/master/examples/cars/bin/cars/main.go
Without access control
With router you can easy change access control logic, you just need to change or remove middleware call in router definition. For example chaincode without access control of carRegister
function. We modified only two pieces of code — Init function and registration carRegister
function in router, while carRegister
function body remained unchanged.
https://github.com/s7techlab/cckit/blob/master/examples/cars/cars_noacces_control.go
and chaincode main file without access control
https://github.com/s7techlab/cckit/blob/master/examples/cars/bin/cars_noac/main.go
Testing the chaincode
For testing we use the extended version of the MockStub
to which we added Creator
mocking and some sugar to method invocation.
More about chaincode testing will be in the next article