前面介紹了hyperledger Fabric 安裝, Chaincode的開發和運維, 如今來講說hyperledger fabric的客戶端相關的開發。hyperledger 的客戶端開發, 實際上指的是Chaincode的客戶端開發。html
同傳統的互聯網開發同樣, 能夠理解爲hyperledger fabric是C/S架構, 固然這樣的類比不是很嚴謹。那麼, 之前的服務端API在hyperledger fabric中至關於Chaincode開發, 之前的容器或者其餘相似與Tomcat, Nginx 的服務器至關於 hyperledger 中的 Blockchain 自己, 固然在Blockchain中, 數據存儲也在Blockchain上, 因此Blochchain也是新的存儲平臺, 而傳統的客戶端開發, 其實就是經過SDK或者Restful APIs 和服務端進行交互, 在hyperledger中, 一樣可使用SDK 或者 Restful APIs 來和服務端進行交互, 只是, 這個交互不單單適合本身定義的Chaincode中的業務邏輯來進行交互, 也和Chaincode自己進行交互, 在hyperledger中, 例如客戶端application 經過SDK鏈接訪問 peer, channel, orderer , Block, Transaction等。java
總的來講, Hyperledger Fabric 客戶端開發主要包括經過Chaincode定義業務邏輯, 來改變Blockchain的狀態, 經過SDK來和Blockchain進行通信。node
Hyperledger Fabric 提供了多種語言的SDK版本, 目前主要包括:git
官方支持的版本:github
非官方的版本:json
其中, 以 Hyperledger Fabric Node SDK的文檔最爲詳細, 這裏以Node SDK 爲例來講明Hyperledger Fabric客戶端開發。api
在 Hyperledger Fabric 的SDK中, 提供的API的做用主要有:安全
在 交易流程 中提供了一個應用程序(SDK), peer 和 orderer 共同處理事物併產生區塊的流程。Fabric的安全是經過數字簽名來實現的, 在Fabric中全部的請求都必須具備有效註冊證書的用戶簽名。對於在Fabric中被認爲有效的證書, 必須具備受信任的證書頒發機構簽名。Fabirc支持CA的全部標準, Fabric 同時提供了一個可選的CA實現。服務器
Node SDK由3個頂級模塊組成:網絡
fabric-client:
鏈接一個節點的事件流
監控一個區塊事件
監控交易事件和結果
堅挺鏈碼自定義事件
fabric-ca-client:
下面經過一個實例來講明Hyperledger Fabric客戶端開發。一下是一段Chaincode, 定義了業務邏輯。
【有點相似傳統的管理系統開發, chaincode實現CURD的功能, 經過SDK與fabric 交互, 來達到Blockchain狀態的改變, 只是, chaincode支持byte和json數據,而且是以KV的形式存儲】
/* # Copyright IBM Corp. All Rights Reserved. # # SPDX-License-Identifier: Apache-2.0 */ 'use strict'; const shim = require('fabric-shim'); const util = require('util'); let Chaincode = class { // The Init method is called when the Smart Contract 'fabcar' is instantiated by the blockchain network // Best practice is to have any Ledger initialization in separate function -- see initLedger() async Init(stub) { console.info('=========== Instantiated fabcar chaincode ==========='); return shim.success(); } // The Invoke method is called as a result of an application request to run the Smart Contract // 'fabcar'. The calling application program has also specified the particular smart contract // function to be called, with arguments async Invoke(stub) { let ret = stub.getFunctionAndParameters(); console.info(ret); let method = this[ret.fcn]; if (!method) { console.error('no function of name:' + ret.fcn + ' found'); throw new Error('Received unknown function ' + ret.fcn + ' invocation'); } try { let payload = await method(stub, ret.params); return shim.success(payload); } catch (err) { console.log(err); return shim.error(err); } } async queryCar(stub, args) { if (args.length != 1) { throw new Error('Incorrect number of arguments. Expecting CarNumber ex: CAR01'); } let carNumber = args[0]; let carAsBytes = await stub.getState(carNumber); //get the car from chaincode state if (!carAsBytes || carAsBytes.toString().length <= 0) { throw new Error(carNumber + ' does not exist: '); } console.log(carAsBytes.toString()); return carAsBytes; } async initLedger(stub, args) { console.info('============= START : Initialize Ledger ==========='); let cars = []; cars.push({ make: 'Toyota', model: 'Prius', color: 'blue', owner: 'Tomoko' }); cars.push({ make: 'Ford', model: 'Mustang', color: 'red', owner: 'Brad' }); cars.push({ make: 'Hyundai', model: 'Tucson', color: 'green', owner: 'Jin Soo' }); cars.push({ make: 'Volkswagen', model: 'Passat', color: 'yellow', owner: 'Max' }); cars.push({ make: 'Tesla', model: 'S', color: 'black', owner: 'Adriana' }); cars.push({ make: 'Peugeot', model: '205', color: 'purple', owner: 'Michel' }); cars.push({ make: 'Chery', model: 'S22L', color: 'white', owner: 'Aarav' }); cars.push({ make: 'Fiat', model: 'Punto', color: 'violet', owner: 'Pari' }); cars.push({ make: 'Tata', model: 'Nano', color: 'indigo', owner: 'Valeria' }); cars.push({ make: 'Holden', model: 'Barina', color: 'brown', owner: 'Shotaro' }); for (let i = 0; i < cars.length; i++) { cars[i].docType = 'car'; await stub.putState('CAR' + i, Buffer.from(JSON.stringify(cars[i]))); console.info('Added <--> ', cars[i]); } console.info('============= END : Initialize Ledger ==========='); } async createCar(stub, args) { console.info('============= START : Create Car ==========='); if (args.length != 5) { throw new Error('Incorrect number of arguments. Expecting 5'); } var car = { docType: 'car', make: args[1], model: args[2], color: args[3], owner: args[4] }; await stub.putState(args[0], Buffer.from(JSON.stringify(car))); console.info('============= END : Create Car ==========='); } async queryAllCars(stub, args) { let startKey = 'CAR0'; let endKey = 'CAR999'; let iterator = await stub.getStateByRange(startKey, endKey); let allResults = []; while (true) { let res = await iterator.next(); if (res.value && res.value.value.toString()) { let jsonRes = {}; console.log(res.value.value.toString('utf8')); jsonRes.Key = res.value.key; try { jsonRes.Record = JSON.parse(res.value.value.toString('utf8')); } catch (err) { console.log(err); jsonRes.Record = res.value.value.toString('utf8'); } allResults.push(jsonRes); } if (res.done) { console.log('end of data'); await iterator.close(); console.info(allResults); return Buffer.from(JSON.stringify(allResults)); } } } async changeCarOwner(stub, args) { console.info('============= START : changeCarOwner ==========='); if (args.length != 2) { throw new Error('Incorrect number of arguments. Expecting 2'); } let carAsBytes = await stub.getState(args[0]); let car = JSON.parse(carAsBytes); car.owner = args[1]; await stub.putState(args[0], Buffer.from(JSON.stringify(car))); console.info('============= END : changeCarOwner ==========='); } }; shim.start(new Chaincode());