Hyperledger Fabric(三) 官方教程:编写第一个应用

前言

官方英文:Writing Your First Application

中文:编写第一个应用

仍然感谢中文翻译!不过官方的教程有更新。我已经按官方英文进行了补充和修正,但是不保存翻译的准确性。如果希望看完整及原汁原味的文档,推荐看官方英文文档。

本文基于Mac OS以及Hyperledger Fabric 1.1.0

Writing Your First Application - 编写第一个应用

注意:如果你对Fabric的原理及架构还不熟悉,你可能需要先学习IntroductionBuilding Your First Network

这篇文章中,我们会学习Fabric app是如何工作的。这些app(以及它们使用的智能合约),我们把它称为fabcar,提供了许多Fabric函数。尤其是Certificate Authority对进程的影响,以及生成证书登记。然后我们会使用这些生成的身份(用户对象)来查询和更新账本。

通过以下三个步骤:

  1. 设置一个开发环境。我们的应用需要一个网络环境,所以我们下载一些组件,以便进行注册/登记,查询和更新。
  1. 学习应用程序中所用到的智能合约例子的参数。智能合约包含的各种功能让我们可以用多种方式和账本进行交互。

  2. 开发能够查询以及更新资产的应用程序。我们会进入到app的代码内部(我们的app使用Javascript编写),手动修改变量来进行不同方式的查询和更新。

完成本教程之后,你应该会基本了解一个带有智能合约的应用程序如何编码,以及如何与Fabric网络中的账本(或者节点)进行交互的。

设置开发环境

首先配置开发环境,下载fabric-samples代码以及镜像文件。这些内容请参考Hyperledger Fabric 1.1.0环境搭建

现在进入到文件夹,然后看看里面都有些什么:

yuyangdeMacBook-Pro:~ yuyang$ cd /Users/yuyang/fabric-sample/fabric-samples/fabcar 
yuyangdeMacBook-Pro:fabcar yuyang$ ls
enrollAdmin.js  package.json    registerUser.js
invoke.js   query.js    startFabric.sh

在开始之前,我们先做些清理工作。执行以下命令以关闭旧的或者启动着的容器:

yuyangdeMacBook-Pro:fabcar yuyang$ docker rm -f $(docker ps -aq)

清除网络中的缓存:

# Press 'y' when prompted by the command

yuyangdeMacBook-Pro:fabcar yuyang$ docker network prune
WARNING! This will remove all networks not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Networks:
net_byfn

最后如果你准备好运行,你可能需要删除潜在的fabcar智能合约镜像。如果是第一次运行,你将不需要这些在你系统中的链码镜像。

yuyangdeMacBook-Pro:fabcar yuyang$ docker rmi dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba

安装客户端&启动网络

注意:接下来的命令都是运行在fabcar文件夹中的。

执行以下命令为应用安装Fabric依赖。fabric-ca-client允许我们的app连接CA server并且检索身份材料。fabric-client允许我们获取身份材料,并且与节点和排序服务对话。

yuyangdeMacBook-Pro:fabcar yuyang$ npm install

使用startFabric.sh脚本启动网络。这个命令会初始化各种Fabric实体,还会启动一个使用Golang编写的智能合约容器。

./startFabric.sh

你也可以使用Node.js编写的链码,将刚才的指令修改为以下命令启动:

./startFabric.sh node

好了,现在我们有了简单的网络以及一些代码,现在看看他们是怎么一起工作的。

应用程序如何与网络进行交互(How Applications Interact with the Network)

如果你想深入了解fabcar网络中的组件(以及怎样部署的),更新详细的了解应用和组件是如何交互的,可以阅读Understanding the Fabcar Network

登记管理员用户

接下来的两部分内容都与CA的通讯有关,你可以通过查看CA的日志来获得更多信息。

新开一个命令行窗口,输入以下指令查看CA日志:

yuyangdeMacBook-Pro:fabcar yuyang$ docker logs -f ca.example.com

现在回到fabcar上来... ...

当我们启动网络时,我们通过Certificate Authority注册了一个管理员用户admin。现在我们需要向CA服务器发送一个登记请求,然后为其取回一个登记证书。这里我们不深入登记的细节,只要知道这个证书是构成管理员用户的必要条件。我们随后会使用这个管理员来注册和登记新的用户。现在向CA服务器发送管理员登记请求:

yuyangdeMacBook-Pro:fabcar yuyang$ node enrollAdmin.js
Store path:/Users/yuyang/fabric-sample/fabric-samples/fabcar/hfc-key-store
Successfully enrolled admin user "admin"
Assigned the admin user to the fabric client ::{"name":"admin","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"","enrollment":{"signingIdentity":"c5e8730cb530c1e2db68c233cb8684023032451560db835a2404c2db6d9ff757","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICAjCCAaigAwIBAgIUCbtDEKsU4rC1FqRcX2F/Ilwv6WAwCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTgwMzIxMDQzNTAwWhcNMTkwMzIxMDQ0\nMDAwWjAhMQ8wDQYDVQQLEwZjbGllbnQxDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAEEc/eyDHDU5aPW5x1+5FojpsFWc4kgKDjflx30uZl\nXRZXmonEgYuVQYyVMHSaDrRjiTc1aewion01/4CSwCHodKNsMGowDgYDVR0PAQH/\nBAQDAgeAMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFP/+uwBM8hgooR+DggGmMmKR\nXiijMCsGA1UdIwQkMCKAIEI5qg3NdtruuLoM2nAYUdFFBNMarRst3dusalc2Xkl8\nMAoGCCqGSM49BAMCA0gAMEUCIQDwpO9f41gNPnvG5CitVUIe/Df5/2elTL2uMKcq\n/jUi4gIgCRtU78R7rJIjSo1UPo61I5O60zgQRkUJfl6o0xHEVKg=\n-----END CERTIFICATE-----\n"}}}

这行代码会调用一个证书签名请求(CSR),最后会在项目根目录生成一个新的文件夹hfc-key-store,里面包含了证书和密钥材料。当我们的app需要创建和读取不同身份用户时,需要定位到此文件夹。

User1的注册和登记(Register and Enroll user1)

使用刚刚生成的管理员证书,我们再一次联通CA服务器来注册和登记一个新用户。user1是我们用来查询和更新账本的用户。这里着重说明的是,admin发起了新用户的注册和登记工作(就好像admin扮演了登记员的角色)。现在为admin发起登记和注册请求:

yuyangdeMacBook-Pro:fabcar yuyang$ node registerUser.js
Store path:/Users/yuyang/fabric-sample/fabric-samples/fabcar/hfc-key-store
Successfully loaded admin from persistence
Successfully registered user1 - secret:ErdiJNAOBHAN
Successfully enrolled member user "user1" 
User1 was successfully registered and enrolled and is ready to intreact with the fabric network

和管理员登记一样,上面的指令会调用CSR然后将证书和密钥放入hfc-key-store文件夹中。现在我们有了两个用户的身份材料。是时候与账本交互了。

查询账本(Querying the Ledger)

查询是指如何从账本中读取数据。您可以查询单个或者多个键的值,如果账本是以类似于JSON这样的数据存储格式写入的,则可以执行更复杂的搜索(如查找包含某些关键字的所有资产)。

下图是一个查询流程的示意图:

首先,运行query.js 程序,返回账本上所有汽车列表。我们使用user1作为签名实体。我们的程序中已经指定了user1为签名实体:

fabric_client.getUserContext('user1', true);

user1的登记材料已经放在了hfc-key-store文件夹中,我们只需要简单的告诉程序去获取它就行了。在定义了用户对象后,我们继续读取账本的流程。queryAllCars这个方法已经被提前定义在了app中,它可以查询所有的cars。执行以下指令:

yuyangdeMacBook-Pro:fabcar yuyang$ node query.js

返回应如下:

Store path:/Users/yuyang/fabric-sample/fabric-samples/fabcar/hfc-key-store
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的黑色Tesla Model S、一辆属于Brad的红色Ford Mustang、一辆属于Pari的紫罗兰色Fiat Punto等等。账本是基于Key/Value 的,在这里,关键字是从CAR0CAR9。这一点特别重要。

现在让我们来看看代码内容。使用编辑器(例如atom或visual studio)打开query.js程序。

应用程序的初始部分定义了变量,如链码,通道名称和网络端点。在我们的app中,这些变量已经定义好了,但是在真实的开发中,这些变量应该又开发者指定。

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

里面也有使用Node.js版本的链码。

你会看到,我们可以调用下面的函数- initLedgerqueryCarqueryAllCarscreateCarchangeCarOwner

让我们仔细看看queryAllCars函数是如何与账本进行交互的。

func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {

      startKey := "CAR0"
      endKey := "CAR999"

      resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)

这里定义了queryAllCars的范围。在CAR0CAR999的每辆车。因此,我们理论上可以创建1,000辆汽车,queryAllCars函数将会显示出每一辆汽车的信息。

下面的图演示了app如何调用链码上的不同方法。每一个方法都必须在chaincode shim interface中可见的API中编码。这可以使智能合约容器与节点账本有序连接。

我们可以看到我们用过的queryAllCars函数,还有一个叫做createCar,这个函数可以让我们更新账本,并最终在链上增加一个新区块。

现在我们返回query.js程序并编辑请求构造函数以查询特定的车辆。为达此目的,我们将函数queryAllCars更改为queryCar并将特定的“Key” 传递给args参数。在这里,我们使用CAR4。 所以我们编辑后的query.js程序现在应该包含以下内容:

const request = {
  //targets : --- letting this default to the peers assigned to the channel
  chaincodeId: 'fabcar',
  fcn: 'queryCar',
  args: ['CAR4']
};

保存程序并返回fabcar目录。现在再次运行程序:

yuyangdeMacBook-Pro:fabcar yuyang$ node query.js

您应该看到以下内容:

Store path:/Users/yuyang/fabric-sample/fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is  {"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}

这样,我们就从查询所有车变成了只查询一辆车:Adriana的黑色Tesla Model S。使用queryCar函数,我们可以查询任意关键字(例如CAR0),并获得与该车相对应的制造厂商、型号、颜色和所有者。

很好,现在您应该比较熟悉该链码的基本查询功能以及带参数的查询功能了。现在是时候更新账本了…

更新账本(Updating the Ledger)

现在我们已经完成了一些账本查询,并且增加了一些代码,我们准备好更新账本了。有许多种更新账本的方法,不过我们先从创造一辆车开始。

下面的示意图演示了这个流程。

账本更新是从生成交易提案的应用程序开始的。就像查询一样,我们将会构造一个请求,用来识别要进行交易的通道ID、函数以及智能合约。该程序然后调用channel.SendTransactionProposalAPI将交易建议发送给peer(s)进行认证。

网络(即endorsing peer)返回一个提案答复,应用程序以此来创建和签署交易请求。该请求通过调用channel.sendTransaction API发送到排序服务器。排序服务器将把交易打包进区块,然后将区块“发送”到通道上的所有peers进行认证。(在我们的例子中,我们只有一个endorsing peer。)

最后,应用程序使用eh.setPeerAddr API连接到peer的事务监听端口,并调用eh.registerTxEvent注册与特定交易ID相关联的事务。该API使得应用程序获得事务的结果(即成功提交或不成功)。把它当作一个通知机制。

我们初始调用的目标是简单地创建一个新的汽车。我们有一个独立的用于这些交易的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。首先我们创建一个红色的Chevy Volt,并把它归属于Nick。在账本中我们的Key值已经用到了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
};

保存并运行程序:

yuyangdeMacBook-Pro:fabcar yuyang$ node invoke.js

输入如下:

Store path:/Users/yuyang/fabric-sample/fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Assigning transaction_id:  ea460cb160e0d5c15737b35aade81bbaaa7b29ccbd3b34f43929809290c213fa
Transaction proposal was good
Successfully sent Proposal and received ProposalResponse: Status - 200, message - "OK"
The transaction has been committed on peer localhost:7053
Send transaction promise and event listener promise have completed
Successfully sent transaction to the orderer.
Successfully committed the change to the ledger by the peer

我们关心的是这个:

The transaction has been committed on peer localhost:7053

可以看到交易已经被确认。现在回到query.js,然后修改参数CAR4CAR10

修改前:

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']
};

保存后,然后查询:

yuyangdeMacBook-Pro:fabcar yuyang$ node query.js

结果如下:

Store path:/Users/yuyang/fabric-sample/fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is  {"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"}

恭喜!你已经创造了一辆车!

最后,我们来调用最后一个函数changeCarOwner。Nick很慷慨,他想把他的Chevy Volt送给Dave。所以,我们简单编辑invoke.js 如下:

var request = {
  //targets: let default to the peer assigned to the client
  chaincodeId: 'fabcar',
  fcn: 'changeCarOwner',
  args: ['CAR10', 'Dave'],
  chainId: 'mychannel',
  txId: tx_id
};

第一个参数定义了哪辆车被变更主人。第二个参数定义了新主人姓名。

保存并执行:

yuyangdeMacBook-Pro:fabcar yuyang$ node invoke.js

结果如下:

Store path:/Users/yuyang/fabric-sample/fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Assigning transaction_id:  c5f23e3da0812761b47755c3487ad9cf8291a2b756061473ce0b85aa0fce1411
Transaction proposal was good
Successfully sent Proposal and received ProposalResponse: Status - 200, message - "OK"
The transaction has been committed on peer localhost:7053
Send transaction promise and event listener promise have completed
Successfully sent transaction to the orderer.
Successfully committed the change to the ledger by the peer

现在我们去查询账本,看看CAR10是不是已经在Dave名下:

yuyangdeMacBook-Pro:fabcar yuyang$ node query.js

结果如下,Dave拥有了CAR10

Store path:/Users/yuyang/fabric-sample/fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is  {"colour":"Red","make":"Chevy","model":"Volt","owner":"Dave"}

真实情况下,链码需要权限控制。例如只有某些具有权限的人才能创造新车,也应该只有车主才能转让汽车所有权。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容