前面介绍了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());