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 ```