github.com/s7techlab/cckit@v0.10.5/README.md (about)

     1  # Hyperledger Fabric chaincode kit (CCKit)
     2  
     3  [![Go Report Card](https://goreportcard.com/badge/github.com/s7techlab/cckit)](https://goreportcard.com/report/github.com/s7techlab/cckit)
     4  ![Build](https://api.travis-ci.org/s7techlab/cckit.svg?branch=master)
     5  [![Coverage Status](https://coveralls.io/repos/github/s7techlab/cckit/badge.svg?branch=master)](https://coveralls.io/github/s7techlab/cckit?branch=master)
     6  
     7  ## Overview
     8  
     9  A [smart contract](https://hyperledger-fabric.readthedocs.io/en/latest/glossary.html#smart-contract) is code, 
    10  invoked by a client application external to the blockchain network – that manages access and modifications to a set of
    11  key-value pairs in the World State.  In Hyperledger Fabric, smart contracts are referred to as chaincode.
    12  
    13  **CCKit** is a **programming toolkit** for developing and testing Hyperledger Fabric golang chaincodes. It enhances
    14  the development experience while providing developers components for creating more readable and secure
    15  smart contracts.
    16  
    17  ## Chaincode examples
    18  
    19  There are several chaincode "official" examples available: 
    20  
    21  * [Commercial paper](https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/smartcontract.html) from official [Hyperledger Fabric documentation](https://hyperledger-fabric.readthedocs.io)
    22  * [Blockchain insurance application](https://github.com/IBM/build-blockchain-insurance-app) (testing tutorial: how to [write tests for "insurance" chaincode](examples/insurance))
    23  
    24  and [others](docs/chaincode-examples.md)
    25  
    26  **Main problems** with existing examples are: 
    27  
    28  * Working with chaincode state at very low level
    29  * Lots of code duplication (JSON marshalling / unmarshalling, validation, access control, etc)
    30  * Chaincode methods routing appeared only in HLF 1.4 and only in Node.Js chaincode
    31  * Uncompleted testing tools (MockStub)
    32  
    33  ### CCKit features 
    34  
    35  * [Chaincode method router](router) with invocation handlers and middleware capabilities 
    36  * [Chaincode state modeling](state) using [protocol buffers](examples/cpaper_extended) / [golang struct to json marshalling](examples/cars), with [private data support](examples/private_cars)
    37  * Designing chaincode in [gRPC service notation](gateway) with code generation of chaincode SDK, gRPC and REST-API
    38  * [MockStub testing](testing), allowing to immediately receive test results
    39  * [Data encryption](extensions/encryption) on application level
    40  * Chaincode method [access control](extensions/owner)
    41  
    42  ### Publications with usage examples 
    43  
    44  * [Service-oriented Hyperledger Fabric application development using gRPC definitions](https://medium.com/coinmonks/service-oriented-hyperledger-fabric-application-development-32e66f578f9a)
    45  * [Hyperledger Fabric smart contract data model: protobuf to chaincode state mapping](https://medium.com/coinmonks/hyperledger-fabric-smart-contract-data-model-protobuf-to-chaincode-state-mapping-191cdcfa0b78)
    46  * [Hyperledger Fabric chaincode test driven development (TDD) with unit testing](https://medium.com/coinmonks/test-driven-hyperledger-fabric-golang-chaincode-development-dbec4cb78049)
    47  * [ERC20 token as Hyperledger Fabric Golang chaincode](https://medium.com/@viktornosov/erc20-token-as-hyperledger-fabric-golang-chaincode-d09dfd16a339)
    48  * [CCKit: Routing and middleware for Hyperledger Fabric Golang chaincode](https://medium.com/@viktornosov/routing-and-middleware-for-developing-hyperledger-fabric-chaincode-written-in-go-90913951bf08)
    49  * [Developing and testing Hyperledger Fabric smart contracts](https://habr.com/post/426705/) [RUS]
    50  
    51  ## Examples based on CCKit
    52  
    53  * [Cars](examples/cars) - car registration chaincode, *simplest* example
    54  * [Commercial paper, service-oriented approach](https://github.com/s7techlab/hyperledger-fabric-samples) - 
    55    recommended way to start new application. Code generation radically simplifies building on-chain and off-chain applications.
    56  
    57  
    58  * [Commercial paper](examples/cpaper) - faithful reimplementation of the official example 
    59  * [Commercial paper extended example](examples/cpaper_extended) - with protobuf chaincode state schema and other features
    60  * [ERC-20](examples/erc20) - tokens smart contract, implementing ERC-20 interface
    61  * [Cars private](examples/private_cars) - car registration chaincode with private data
    62  * [Payment](examples/payment) - a few examples of chaincodes with encrypted state 
    63   
    64  ## Installation
    65  
    66  CCKit requires Go 1.11+ with modules support
    67  
    68  ### Standalone
    69   
    70  `git clone git@github.com:s7techlab/cckit.git`
    71  
    72  `go mod vendor`
    73  
    74  ### As dependency
    75  
    76  `go get -u github.com/s7techlab/cckit`
    77  
    78  ## Example - Commercial Paper chaincode
    79  
    80  ### Scenario
    81  
    82  [Commercial paper](https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/scenario.html) 
    83  scenario from official documentation describes a Hyperledger Fabric network, aimed to issue, buy and redeem 
    84  commercial paper.
    85  
    86  ![commercial paper network](examples/cpaper/img/cpaper-network.png)
    87  
    88  ### 5 steps to develop chaincode
    89  
    90  Chaincode is a domain specific program which relates to specific business process. The job of a smart
    91  contract developer is to take an existing business process and express it as a smart contract in a
    92  programming language. Steps of chaincode development:
    93  
    94  1. Define chaincode model - schema for state entries, transaction payload and events
    95  2. Define chaincode interface
    96  3. Implement chaincode instantiate method
    97  4. Implement chaincode methods with business logic
    98  5. Create tests
    99  
   100  
   101  ### Define chaincode model
   102  
   103  With protocol buffers, you write a `.proto` description of the data structure you wish to store.
   104  From that, the protocol buffer compiler creates a golang struct that implements automatic encoding
   105  and parsing of the protocol buffer data with an efficient binary format (or json).
   106  
   107  Code generation can be simplified with a short [Makefile](examples/cpaper_extended/schema):
   108  
   109  ```makefile
   110  .: generate
   111  
   112  generate:
   113  	@echo "schema"
   114  	@protoc -I=./ --go_out=./ ./*.proto
   115  ```
   116  
   117  #### Chaincode state
   118  
   119  The following file shows how to define the world state schema using protobuf.
   120  
   121  [examples/cpaper_extended/schema/state.proto](examples/cpaper_extended/schema/state.proto)
   122  
   123  ```proto
   124  syntax = "proto3";
   125  
   126  package cckit.examples.cpaper_extended.schema;
   127  option go_package = "schema";
   128  
   129  import "google/protobuf/timestamp.proto";
   130  
   131  // Commercial Paper state entry
   132  message CommercialPaper {
   133  
   134      enum State {
   135          ISSUED = 0;
   136          TRADING = 1;
   137          REDEEMED = 2;
   138      }
   139  
   140      // Issuer and Paper number comprises composite primary key of Commercial paper entry
   141      string issuer = 1;
   142      string paper_number = 2;
   143  
   144      string owner = 3;
   145      google.protobuf.Timestamp issue_date = 4;
   146      google.protobuf.Timestamp maturity_date = 5;
   147      int32 face_value = 6;
   148      State state = 7;
   149  
   150      // Additional unique field for entry
   151      string external_id = 8;
   152  }
   153  
   154  // CommercialPaperId identifier part
   155  message CommercialPaperId {
   156      string issuer = 1;
   157      string paper_number = 2;
   158  }
   159  
   160  // Container for returning multiple entities
   161  message CommercialPaperList {
   162      repeated CommercialPaper items = 1;
   163  }
   164  ```
   165  
   166  #### Chaincode transaction and events payload
   167  
   168  This file defines the data payload used using in the business logic methods.
   169  In this example transaction and event payloads are exactly the same for the sake of brevity, but you could
   170  create a different schema for each type of payload.
   171  
   172  [examples/cpaper_extended/schema/payload.proto](examples/cpaper_extended/schema/payload.proto)
   173  
   174  ```proto
   175  // IssueCommercialPaper event
   176  syntax = "proto3";
   177  
   178  package cckit.examples.cpaper_extended.schema;
   179  option go_package = "schema";
   180  
   181  import "google/protobuf/timestamp.proto";
   182  import "github.com/mwitkow/go-proto-validators/validator.proto";
   183  
   184  // IssueCommercialPaper event
   185  message IssueCommercialPaper {
   186      string issuer = 1;
   187      string paper_number = 2;
   188      google.protobuf.Timestamp issue_date = 3;
   189      google.protobuf.Timestamp maturity_date = 4;
   190      int32 face_value = 5;
   191  
   192      // external_id - another unique constraint
   193      string external_id = 6;
   194  }
   195  
   196  // BuyCommercialPaper event
   197  message BuyCommercialPaper {
   198      string issuer = 1;
   199      string paper_number = 2;
   200      string current_owner = 3;
   201      string new_owner = 4;
   202      int32 price = 5;
   203      google.protobuf.Timestamp purchase_date = 6;
   204  }
   205  
   206  // RedeemCommercialPaper event
   207  message RedeemCommercialPaper {
   208      string issuer = 1;
   209      string paper_number = 2;
   210      string redeeming_owner = 3;
   211      google.protobuf.Timestamp redeem_date = 4;
   212  }
   213  ```
   214  
   215  ### Define chaincode interface
   216  
   217  In [examples/cpaper_extended/chaincode.go](examples/cpaper_extended/chaincode.go) file we will define the mappings,
   218  chaincode initialization method and business logic in the transaction methods.
   219  For brevity we will only display snippets of the code here, please refer to the original file for full example.
   220  
   221  Firstly we define mapping rules. These specify the struct used to hold a specific chaincode state, it's primary key, list mapping, unique keys, etc.
   222  Then we define the schemas used for emitting events.
   223  
   224  ```go
   225  var (
   226  	// State mappings
   227  	StateMappings = m.StateMappings{}.
   228  		// Create mapping for Commercial Paper entity
   229  		Add(&schema.CommercialPaper{},
   230  			// Key namespace will be <"CommercialPaper", Issuer, PaperNumber>
   231  			m.PKeySchema(&schema.CommercialPaperId{}),
   232  			// Structure of result for List method
   233  			m.List(&schema.CommercialPaperList{}),
   234  			// External Id is unique
   235  			m.UniqKey("ExternalId"),
   236  		)
   237  
   238  	// EventMappings
   239  	EventMappings = m.EventMappings{}.
   240  		// Event name will be "IssueCommercialPaper", payload - same as issue payload
   241  		Add(&schema.IssueCommercialPaper{}).
   242  		// Event name will be "BuyCommercialPaper"
   243  		Add(&schema.BuyCommercialPaper{}).
   244  		// Event name will be "RedeemCommercialPaper"
   245  		Add(&schema.RedeemCommercialPaper{})
   246  )
   247  ```
   248  
   249  CCKit uses [router](router) to define rules about how to map chaincode invocation to a particular handler,
   250  as well as what kind of middleware needs to be used during a request, for example how to convert incoming argument from
   251  []byte to target type (string, struct, etc).
   252  
   253  ```go
   254  func NewCC() *router.Chaincode {
   255  
   256  	r := router.New(`commercial_paper`)
   257  
   258  	// Mappings for chaincode state
   259  	r.Use(m.MapStates(StateMappings))
   260  
   261  	// Mappings for chaincode events
   262  	r.Use(m.MapEvents(EventMappings))
   263  
   264  	// Store in chaincode state information about chaincode first instantiator
   265  	r.Init(owner.InvokeSetFromCreator)
   266  
   267  	// Method for debug chaincode state
   268  	debug.AddHandlers(r, `debug`, owner.Only)
   269  
   270  	r.
   271  		// read methods
   272  		Query(`list`, cpaperList).
   273  
   274  		Query(`get`, cpaperGet, defparam.Proto(&schema.CommercialPaperId{})).
   275  
   276  		// txn methods
   277  		Invoke(`issue`, cpaperIssue, defparam.Proto(&schema.IssueCommercialPaper{})).
   278  		Invoke(`buy`, cpaperBuy, defparam.Proto(&schema.BuyCommercialPaper{})).
   279  		Invoke(`redeem`, cpaperRedeem, defparam.Proto(&schema.RedeemCommercialPaper{})).
   280  		Invoke(`delete`, cpaperDelete, defparam.Proto(&schema.CommercialPaperId{}))
   281  
   282  	return router.NewChaincode(r)
   283  }
   284  ```
   285  
   286  ### Implement chaincode `init` method
   287  
   288  In many cases during chaincode instantiation we need to define permissions for chaincode functions -
   289  "who is allowed to do this thing", incredibly important in the world of smart contracts.
   290  The most common and basic form of access control is the concept of `ownership`: there's one account (combination
   291  of MSP and certificate identifiers) that is the owner and can do administrative tasks on contracts. This 
   292  approach is perfectly reasonable for contracts that only have a single administrative user.
   293  
   294  CCKit provides `owner` extension for implementing ownership and access control in Hyperledger Fabric chaincodes.
   295  In the previous snippet, as an `init` method, we used [owner.InvokeSetFromCreator](extensions/owner/handler.go), storing information
   296  which stores the information about who is the owner into the world state upon chaincode instantiation.
   297  
   298  ### Implement business rules as chaincode methods
   299  
   300  Now we have to define the actual business logic which will modify the world state when a transaction occurs.
   301  In this example we will show only the `buy` method for brevity.
   302  Please refer to [examples/cpaper_extended/chaincode.go](examples/cpaper_extended/chaincode.go) for full implementation.
   303  
   304  ```go
   305  func invokeCPaperBuy(c router.Context) (interface{}, error) {
   306  	var (
   307  		cpaper *schema.CommercialPaper
   308  
   309  		// Buy transaction payload
   310  		buyData = c.Param().(*schema.BuyCommercialPaper)
   311  
   312  		// Get the current commercial paper state
   313  		cp, err = c.State().Get(
   314  			&schema.CommercialPaperId{Issuer: buyData.Issuer, PaperNumber: buyData.PaperNumber},
   315  			&schema.CommercialPaper{})
   316  	)
   317  
   318  	if err != nil {
   319  		return nil, errors.Wrap(err, "not found")
   320  	}
   321  
   322  	cpaper = cp.(*schema.CommercialPaper)
   323  
   324  	// Validate current owner
   325  	if cpaper.Owner != buyData.CurrentOwner {
   326  		return nil, fmt.Errorf(
   327  			"paper %s %s is not owned by %s",
   328  			cpaper.Issuer, cpaper.PaperNumber, buyData.CurrentOwner)
   329  	}
   330  
   331  	// First buyData moves state from ISSUED to TRADING
   332  	if cpaper.State == schema.CommercialPaper_ISSUED {
   333  		cpaper.State = schema.CommercialPaper_TRADING
   334  	}
   335  
   336  	// Check paper is not already REDEEMED
   337  	if cpaper.State == schema.CommercialPaper_TRADING {
   338  		cpaper.Owner = buyData.NewOwner
   339  	} else {
   340  		return nil, fmt.Errorf(
   341  			"paper %s %s is not trading.current state = %s",
   342  			cpaper.Issuer, cpaper.PaperNumber, cpaper.State)
   343  	}
   344  
   345  	if err = c.Event().Set(buyData); err != nil {
   346  		return nil, err
   347  	}
   348  
   349  	return cpaper, c.State().Put(cpaper)
   350  }
   351  ```
   352  
   353  ### Test chaincode functionality
   354  
   355  And finally we should write tests to ensure our business logic is behaving as it should.
   356  Again, for brevity, we omitted most of the code from [examples/cpaper_extended/chaincode_test.go](examples/cpaper_extended/chaincode_test.go).
   357  CCKit support chaincode testing with [Mockstub](testing).
   358  
   359  ```go
   360  var _ = Describe(`CommercialPaper`, func() {
   361  	paperChaincode := testcc.NewMockStub(`commercial_paper`, NewCC())
   362  
   363  	BeforeSuite(func() {
   364  		// Init chaincode with admin identity
   365  		expectcc.ResponseOk(
   366  			paperChaincode.
   367  				From(testdata.GetTestIdentity(MspName, path.Join("testdata", "admin", "admin.pem"))).
   368  				Init())
   369  	})
   370  
   371  	Describe("Commercial Paper lifecycle", func() {
   372  		// ...
   373  
   374  		It("Allow buyer to buy commercial paper", func() {
   375  			buyTransactionData := &schema.BuyCommercialPaper{
   376  				Issuer:       IssuerName,
   377  				PaperNumber:  "0001",
   378  				CurrentOwner: IssuerName,
   379  				NewOwner:     BuyerName,
   380  				Price:        95000,
   381  				PurchaseDate: ptypes.TimestampNow(),
   382  			}
   383  
   384  			expectcc.ResponseOk(paperChaincode.Invoke(`buy`, buyTransactionData))
   385  
   386  			queryResponse := paperChaincode.Query("get", &schema.CommercialPaperId{
   387  				Issuer:      IssuerName,
   388  				PaperNumber: "0001",
   389  			})
   390  
   391  			paper := expectcc.PayloadIs(queryResponse, &schema.CommercialPaper{}).(*schema.CommercialPaper)
   392  
   393  			Expect(paper.Owner).To(Equal(BuyerName))
   394  			Expect(paper.State).To(Equal(schema.CommercialPaper_TRADING))
   395  
   396  			Expect(<-paperChaincode.ChaincodeEventsChannel).To(BeEquivalentTo(&peer.ChaincodeEvent{
   397  				EventName: `BuyCommercialPaper`,
   398  				Payload:   testcc.MustProtoMarshal(buyTransactionData),
   399  			}))
   400  
   401  			paperChaincode.ClearEvents()
   402  		})
   403  
   404  		// ...
   405  
   406  	})
   407  })
   408  ```