Hyperledger Fabric(编写你的第一个应用程序)
编写第一个应用程序
如果你还不熟悉Fabric网络的基本架构,则可能需要在继续之前访问“介绍”和“构建你的第一个网络”文档。
在本节中,我们将介绍一些示例程序,以了解Fabric应用程序的工作原理,这些应用程序(以及他们使用的智能合约) - 统称为fabcar - 提供了Fabric功能的广泛演示。值得注意的是,我们将展示与证书颁发机构进行交互并生成注册证书的过程,之后我们将利用这些身份来查询和更新分类帐。
我们将经历三个主要步骤:
1.设置开发环境。我们的应用程序需要一个网络进行交互,因此我们将下载一个仅限于我们所需的注册/登记,查询和更新的组件:
2.学习我们的应用程序将使用的示例智能合约的参数。我们的智能合约包含各种功能,使我们能够以不同的方式与分类帐进行交互,我们将进入并检查该智能合约,以了解我们的应用程序将使用的功能。
3.开发应用程序以便能够在分类帐上查询和更新资产。我们将进入应用程序代码本身(我们的应用程序已用Javascript编写)并手动操作变量以运行不同类型的查询和更新。
完成本教程后,你应该基本了解应用程序如何与智能合约一起编程,以与Fabric网络上的分类帐(即对等方)进行交互。
设置开发环境
如果你已经完成了构建你的第一个网络,你应该已经设置了你的开发环境,并且已经下载了fabric-samples以及随附的工件。要运行本教程,你现在需要做的就是拆除现有的任何网络,你可以通过发出以下命令来执行此操作:
./byfn.sh down
如果你没有开发环境以及网络和应用程序的附带工件,访问“前提条件”页面,确保你的计算机上已安装必要的依赖项。
接下来,如果你还没有这样做的话,访问”安装示例、二进制文件和Docker镜像“页面并按照提供的说明进行操作。克隆fabric-samples
存储库后返回本教程,并下载最新的稳定Fabric镜像和可用实用程序。
此时所有的都应该已安装,导航到fabric-samples
存储库中的fabcar
子目录,并查看内部的内容:
cd fabric-samples/fabcar && ls
你应该看到以下内容:
enrollAdmin.js invoke.js package.json query.js registerUser.js startFabric.sh
在开始之前,我们还需要做一些家务,运行以下命令以终止任何陈旧或活动容器:
docker rm -f $(docker ps -aq)
清除所有缓存的网络:
# Press 'y' when prompted by the command docker network prune
最后,如果你已经完成本教程,你还需要删除fabcar
智能合约的基础链码镜像,如果你是第一次浏览此内容的用户,那么你的系统上将不会显示此链码镜像:
docker rmi dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba
安装客户端并启动网络
以下说明要求你位于fabric-samples
repo的本地克隆中的fabcar
子目录中,在本教程的其余部分中,保留在此子目录的根目录下。
运行以下命令以安装应用程序的Fabric依赖项,我们关注fabric-ca-client
,它允许我们的应用程序与CA服务器通信并检索身份资料,还有fabric-client
,它允许我们加载身份资料并与对等点和排序服务交流。
npm install
使用startFabric.sh
shell脚本启动网络,此命令将启动我们的各种Fabric实体,并为Golang编写的链码启动智能合约容器:
./startFabric.sh
你还可以选择针对Node.js中编写的链码运行本教程,如果你想追求这条路线,请发出以下命令:
./startFabric.sh node
请注意,Node.js链码场景大约需要90秒才能完成;也许更长。脚本没有挂起,而是增加的时间是在构建链码镜像时安装fabric-shim的结果。
好了,现在你有一个示例网络和一些代码,让我们来看看不同的部分如何组合在一起。
应用程序如何与网络交互
要更深入地了解我们fabcar网络中的组件(以及它们如何部署)以及应用程序如何在更精细的级别上与这些组件进行交互,请参阅了解Fabcar网络。
开发人员更有兴趣了解应用程序的作用 - 以及查看代码本身以查看应用程序的构建方式 - 应该继续下去,目前,最重要的是要知道应用程序使用软件开发工具包(SDK)来访问允许查询和更新分类帐的API。
登记管理员用户
以下两节涉及与证书颁发机构的通信,你可能会发现当运行即将到来的程序时流式传输CA日志很有用。
要流式传输CA日志,请拆分终端或打开新shell并发出以下命令:
docker logs -f ca.example.com
现在回到带着fabcar
内容的终端……
当我们启动我们的网络,管理员用户 - admin
- 已在我们的证书颁发机构注册,现在我们需要向CA服务器发送登记调用,并为该用户检索登记证书(eCert)。我们不会在这里深入研究登记详情,但只需说SDK和扩展我们的应用程序需要此证书才能为管理员形成用户对象,然后我们将使用此管理员对象随后注册并登记新用户,发送管理员登记调用到CA服务器:
node enrollAdmin.js
该程序将调用证书签名请求(CSR)并最终将eCert和密钥材料输出到新创建的文件夹 - hfc-key-store
- 该项目的根目录下,当我们的应用需要为各种用户创建或加载身份对象时会查看此位置。
注册并登记user1
使用我们新生成的管理员eCert,我们现在将再次与CA服务器通信以注册和登记新用户。此用户 - user1
- 将是我们在查询和更新分类帐时使用的身份。这里需要注意的是,管理员身份是为我们的新用户发出注册和登记调用(即该用户扮演注册员的角色),发送为user1注册并登记的调用:
node registerUser.js
与管理员登记类似,该程序调用CSR并将密钥和eCert输出到hfc-key-store
子目录中,所以现在我们有两个独立用户的身份资料 - admin
和user1
,是时候与分类账互动了......
查询分类帐
查询是你从分类帐中读取数据的方式,该数据存储为一系列键值对,并且你可以查询单个键,多个键的值,或者 - 如果分类帐是以JSON等丰富的数据存储格式编写的 - 对其执行复杂搜索(例如,查找包含特定关键字的所有资产)。
这是查询如何工作的表示:
首先,让我们运行query.js
程序,返回分类帐中所有汽车的清单,我们将使用我们的第二个身份 - user1
- 作为此应用程序的签名实体,我们程序中的以下行将user1指定为签名者:
fabric_client.getUserContext('user1', true);
回想一下,user1
登记资料已经放入我们的hfc-key-store
子目录中,所以我们只需要告诉我们的应用程序获取该身份,随着用户对象的定义,我们现在可以从分类账中继续读取。一个查询所有汽车的功能,queryAllCars
,已预先加载到应用程序中,因此我们可以简单地按原样运行程序:
node query.js
它应该返回这样的东西:
Successfully loaded user1 from persistence Query has completed, checking results Response is [{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}}, {"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}}, {"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}}, {"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}}, {"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}}, {"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}}, {"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}}, {"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}}, {"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}}, {"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]
这些是10辆车,Adriana拥有的黑色特斯拉Model S,Brad拥有的红色福特Mustang,Pari拥有的紫罗兰菲亚特Punto,等等。分类帐是基于键值的,在我们的实现中,键是CAR0
到CAR9
,这将在一瞬间变得特别重要。
让我们仔细看看这个程序,使用编辑器(例如atom或visual studio)并打开query.js
。
应用程序的初始部分定义了某些变量,例如通道名称,证书存储位置和网络端点,在我们的示例应用中,这些变量已被植入,但在真正的应用程序中,这些变量必须由app dev指定。
var channel = fabric_client.newChannel('mychannel'); var peer = fabric_client.newPeer('grpc://localhost:7051'); channel.addPeer(peer); var member_user = null; var store_path = path.join(__dirname, 'hfc-key-store'); console.log('Store path:'+store_path); var tx_id = null;
这是我们构造查询的块:
// queryCar chaincode function - requires 1 argument, ex: args: ['CAR4'], // queryAllCars chaincode function - requires no arguments , ex: args: [''], const request = { //targets : --- letting this default to the peers assigned to the channel chaincodeId: 'fabcar', fcn: 'queryAllCars', args: [''] };
当应用程序运行时,它调用了对等点的fabcar
链码,在其中运行queryAllCars
函数,并且没有传递任何参数。
要了解我们智能合约中的可用功能,导航到fabric-samples
根目录下的chaincode/fabcar/go
子目录,并在编辑器中打开fabcar.go
。
这些相同的功能在fabcar
链码的Node.js版本中定义。
你将看到我们可以调用以下函数:initLedger
,queryCar
,queryAllCars
,createCar
和changeCarOwner
。
让我们仔细看看queryAllCars
函数,看看它如何与分类帐交互。
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { startKey := "CAR0" endKey := "CAR999" resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
这定义了queryAllCars
的范围,CAR0
和CAR999
之间的每辆车 - 总共1,000辆车,假设每个键都已正确标记 - 将由查询返回。
下面是一个应用程序如何在链码中调用不同函数的表示,每个函数都必须在链码shim接口中针对可用的API进行编码,这反过来又允许智能合约容器与对等点分类帐正确连接。
我们可以看到我们的queryAllCars
函数,以及一个名为createCar
的函数,它将允许我们更新分类帐并最终在一瞬间将新区块附加到链中。
但首先,返回query.js
程序并编辑构造函数请求以查询CAR4
,我们通过将query.js
中的函数从queryAllCars
更改为queryCar
并将CAR4
作为特定键传递来完成此操作。
query.js
程序现在看起来应该是这样的:
const request = { //targets : --- letting this default to the peers assigned to the channel chaincodeId: 'fabcar', fcn: 'queryCar', args: ['CAR4'] };
保存程序并导航回fabcar
目录,现在再次运行程序:
node query.js
你应该看到以下内容:
{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
如果你回过头来看看我们之前查询过每辆车的结果,你可以看到CAR4
是Adriana的黑色特斯拉型号S,这是在这里返回的结果。
使用queryCar
函数,我们可以查询任何键(例如CAR0
)并获取与该车相对应的任何品牌,型号,颜色和所有者。
很好,此时你应该熟悉智能合约中的基本查询功能以及查询程序中的少数参数,是时候更新分类帐.....
更新分类帐
现在我们已经完成了一些分类帐查询并添加了一些代码,我们准备更新分类帐,我们可以做很多潜在的更新,但让我们从创建汽车开始吧。
下面我们可以看到这个过程如何运作。一个更新提案,认可,然后返回到应用程序,然后将其发送给每个对等点的分类帐:
我们对分类账的第一次更新将是创建一辆新车,我们有一个单独的Javascript程序 - invoke.js
- 我们将用它来进行更新。就像查询一样,使用编辑器打开程序并导航到我们构造调用的代码块:
// createCar chaincode function - requires 5 args, ex: args: ['CAR12', 'Honda', 'Accord', 'Black', 'Tom'], // changeCarOwner chaincode function - requires 2 args , ex: args: ['CAR10', 'Barry'], // must send the proposal to endorsing peers var request = { //targets: let default to the peer assigned to the client chaincodeId: 'fabcar', fcn: '', args: [''], chainId: 'mychannel', txId: tx_id };
你将看到我们可以调用两个函数之一 - createCar
或changeCarOwner
,首先,让我们创建一个红色雪佛兰Volt并将其交给名为Nick的所有者,我们的分类帐上有CAR9
,所以我们在这里使用CAR10
作为识别键,编辑此代码块如下所示:
var request = { //targets: let default to the peer assigned to the client chaincodeId: 'fabcar', fcn: 'createCar', args: ['CAR10', 'Chevy', 'Volt', 'Red', 'Nick'], chainId: 'mychannel', txId: tx_id };
保存并运行程序:
node invoke.js
终端中会有一些关于ProposalResponse
和promises的输出,但是,我们所关注的是这条消息:
The transaction has been committed on peer localhost:7053
查看该交易是否已写入,返回query.js
并将参数从CAR4
更改为CAR10
。
换句话说,改变这一点:
const request = { //targets : --- letting this default to the peers assigned to the channel chaincodeId: 'fabcar', fcn: 'queryCar', args: ['CAR4'] };
为这样:
const request = { //targets : --- letting this default to the peers assigned to the channel chaincodeId: 'fabcar', fcn: 'queryCar', args: ['CAR10'] };
再次保存,然后查询:
node query.js
应该返回这些:
Response is {"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"}
恭喜,你创造了一辆车!
所以现在我们已经做到了,让我们说Nick感到很慷慨,他想把他的雪佛兰伏特送给名叫Dave的人。
要做到这一点,请返回invoke.js
并将函数从createCar
更改为changeCarOwner
并输入如下参数:
var request = { //targets: let default to the peer assigned to the client chaincodeId: 'fabcar', fcn: 'changeCarOwner', args: ['CAR10', 'Dave'], chainId: 'mychannel', txId: tx_id };
第一个参数 - CAR10
- 反映了将改变拥有者的汽车,第二个参数 - Dave
- 定义了汽车的新拥有者。
再次保存并执行程序:
node invoke.js
现在让我们再次查询分类帐并确保Dave现在与CAR10
键相关联:
node query.js
它应该返回这个结果:
Response is {"colour":"Red","make":"Chevy","model":"Volt","owner":"Dave"}
CAR10
的所有权已从Nick改为Dave。
在现实世界的应用程序中,链码可能具有一些访问控制逻辑,例如,只有某些授权用户可以创建新车,并且只有车主可以将车转移给其他人。
总结
现在我们已经完成了一些查询和一些更新,你应该非常清楚应用程序如何与网络交互,已经看到了智能合约,API和SDK在查询和更新中扮演的角色的基础知识,你应该了解如何使用不同类型的应用程序来执行其他业务任务和操作。
在随后的文档中,我们将学习如何实际编写智能合约,以及如何利用其中一些更低级别的应用程序功能(特别是与身份和成员资格服务相关)。
其他资源
Hyperledger Fabric Node SDK repo是深入文档和示例代码的绝佳资源,你还可以在Hyperledger Rocket Chat上咨询Fabric社区和组件专家。