Hyperledger Fabric 开发——Node.js SDK与fabric链码交互

1、本篇背景

前面已经对链码开发作了比较详细的介绍,并且对官方提供的 fabcar 链码进行了解读,本篇将介绍如何使用 Node.js SDK 与区块链网络中的链码进行交互。

本篇内容基本来自官方 Hyperledger Fabric 文档中的 Writing Your First Application 章节,对文档进行翻译,原文网址如下:

http://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html

主要根据谷歌来翻译的,并稍微做一些修改。由于水平有限,最终的翻译质量不太好,欢迎大家拍砖。

2、编写你的第一个应用

提示:
如果您还不熟悉Fabric网络的基础架构,则可能需要在继续之前访问 简介建立您的第一个网络 文档。

在本节中,我们将查看一些示例程序,以了解Fabric应用程序的工作方式。这些应用程序(以及他们使用的智能合约) - 统称为 fabcar --提供了Fabric功能的广泛演示。值得注意的是,我们将展示与证书颁发机构交互并生成注册证书的过程,之后我们将利用这些身份来查询和更新账本。

我们将通过三个主要步骤:
1.建立一个开发环境。我们的应用程序需要一个网络来进行交互,因此我们将下载一个简化为注册/登记,查询和更新所需的组件:

应用程序与链码交互概述

2.学习我们的应用将使用的示例智能合约的参数。我们的智能合约包含各种功能,使我们能够以不同的方式与账本进行交互。 我们将进入并检查该智能合约,以了解我们的应用程序将使用的功能。

3.开发应用程序以便能够查询和更新Fabric记录。 我们将自己进入应用程序代码(我们的应用程序已经使用JavaScript编写),并手动操作变量以运行不同类型的查询和更新。

完成本教程后,您应该基本了解如何将应用程序与智能合约一起编程,以便与Fabric网络上的账本(即节点peer)进行交互。

3、设置您的开发环境

如果您已经完成 建立您的第一个网络,您应该设置好您的开发环境,并下载好 fabric-samples 以及附带的工件。要运行本教程,您现在需要做的是移除您拥有的任何现有网络,您可以通过执行以下操作来完成此操作:

./byfn.sh down

如果您没有开发环境以及网络和应用程序的附带工件,请访问 先决条件 页面,并确保您的计算机上安装了必要的依赖项。

接下来,如果您尚未这样做,请访问安装示例,二进制文件和Docker镜像 页面并按照提供的说明进行操作。

克隆 fabric-samples 库后,返回到本教程,并下载最新稳定版的Fabric镜像和可用的工具。

此时应该安装好了一切。进入到 fabric-samples 库中的 fabcar 子目录,并查看内部内容:

cd fabric-samples/fabcar  && ls

你应该看到以下内容:

enrollAdmin.js  invoke.js   package.json    registerUser.js
hfc-key-store   node_modules    query.js    startFabric.sh
fabcar所在目录

在开始之前,我们还需要做一点准备工作。运行以下命令来杀死当前运行或者活跃的容器:

docker rm -f $(docker ps -aq)

清除所有缓存网络:

# Press 'y' when prompted by the command
# 命令提示时请输入'y'

docker network prune

最后,如果您已经完成了本教程,您还需要删除 fabcar 智能合约的底层链码镜像。如果您是第一次浏览此内容的用户,那么您的系统上不会有此链接代码镜像:

docker rmi dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba
3.1 安装客户端并启动网络

提示:
以下说明需要您在克隆在本地的 fabric-samples 库中的 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的结果。

好吧,现在你已经有了一个示例网络和一些代码,让我们来看看不同部分如何组合在一起。

4、应用程序如何与网络进行交互

要更深入地了解我们 fabcar 网络中的组件(以及它们如何部署)以及应用程序如何与更细粒度级别的组件进行交互,请参阅了解Fabcar网络

开发者更感兴趣的是应用程序之间作了什么 - 以及查看代码本身以了解应用程序是如何构建的。目前,更需要了解的事情是,应用程序使用软件开发工具包(SDK)访问允许查询和更新账本的API。

5、注册管理员用户

提示:
以下两节涉及与证书颁发机构的通信。在运行即将推出的程序时,您可能会发现流式传输CA日志很有用。

要流式处理您的CA日志,拆分您的终端或打开一个新的shell并发出以下命令:

docker logs -f ca.example.com

现在返回到您的终端 fabcar 内容。。。

当我们启动我们的网络时,管理员用户 - admin - 已在我们的认证中心注册。现在我们需要向CA服务器发送一个注册呼叫,并为该用户检索注册证书(eCert)。我们不会在这里详细介绍注册的详细信息,但可以说SDK和扩展我们的应用程序需要此证书才能形成管理员的用户对象。然后我们将使用这个管理对象来注册并注册一个新用户。将管理员注册呼叫发送到CA服务器:

node enrollAdmin.js

该程序将调用证书签名请求(CSR),并最终将eCert和密钥材料输出到此项目的根目录中新创建的文件夹- hfc-key-store 中。然后,我们的应用程序将在他们需要为我们的各种用户创建或加载身份对象时查找此位置。

6、注册普通用户user1

使用我们新生成的管理员eCert,我们现在将再次与CA服务器进行通信,以注册和注册新用户。这个user - user1 - 将是我们在查询和更新账本时使用的身份。这里需要注意的一点是,为我们的新用户发布注册和注册呼叫的管理员身份(即,此用户正在扮演注册员的角色)。发送注册并为 user1 注册呼叫:

node registerUser.js

注:终端显示如下

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

与管理员注册类似,此程序调用CSR并将密钥和eCert输出到 hfc-key-store 子目录中。所以现在我们拥有两个独立用户的身份资料 - adminuser1 。 与账本交互的时间。。。

7、查询账本

查询是指如何从账本中读取数据。这些数据存储为一系列键值对,您可以查询单个键,多个键的值,或者 - 如果账本是使用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拥有的红色Ford Mustang,由Pari拥有的紫色Fiat Punto等等。账本是基于关键值的,在我们的实施中,关键是 CAR0CAR9 。这一点将变得特别重要。

让我们仔细看看这个程序。 使用编辑器(例如atom或visual studio)并打开 query.js

应用程序的初始部分定义了某些变量,例如通道名称,证书存储位置和网络端点。在我们的示例应用程序中,这些变量已被内置,但在真实应用程序中,这些变量必须由应用程序开发者指定。

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

当应用程序运行时,它调用节点peer上的 fabcar 链码,在其中运行 queryAllCars 函数,并且不传递任何参数。

要查看我们智能合约中的可用功能,进入到 fabric-samples 根目录下的 chaincode/fabcar/go 子目录,然后在您的编辑器打开 fabcar.go

提示:
这些相同的功能在 fabcar 链码的Node.js版本中定义。

你会看到我们有以下功能可供调用:initLedgerqueryCarqueryAllCarscreateCarchangeCarOwner

让我们仔细看看 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进行编码,这反过来又允许智能合同容器与对等账本正确对接。


运行Sample

我们可以看到我们的 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 )并获取与该车相对应的任何品牌,型号,颜色和所有者。

很好。此时,您应该熟悉智能合约中的基本查询功能以及查询程序中的少量参数。 时间更新账本。。。

8、更新账本

现在我们已经完成了几个账本查询并添加了一些代码,我们已经准备好更新账本。有很多潜在的更新我们可以做,但我们先创建一辆新车。

下面我们可以看到这个过程如何工作。提案更新,通过认可,然后返回到应用程序,然后将其发送到订单并写入每个节点peer账户:


更新账本

我们对账本的第一次更新将是创造一辆新车。我们有一个单独的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
};

您会看到我们可以调用两个函数之一 - createCarchangeCarOwner 。首先,让我们创建一个红色雪佛兰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 和Promise的输出。 然而,我们所关心的只是这个信息:

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很慷慨,他想把他的雪佛兰Volt交给一个名叫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 的所有权从Dave变成了Dave。

提示:
在现实世界的应用程序中,链码可能会有一些访问控制逻辑。例如,只有特定的授权用户可以创建新车,并且只有车主才可以将车辆转移给其他人。

9、总结

现在,我们已经完成了一些查询和一些更新,您应该对应用程序如何与网络进行交互有一个很好的理解。您已经了解了智能合约,API和SDK在查询和更新中扮演的角色的基本知识,您应该了解如何使用不同类型的应用程序来执行其他业务任务和操作。

在随后的文档中,我们将学习如何实际编写智能合约,以及如何利用这些更低级别的应用程序功能中的一些功能(特别是与身份和会员服务有关的功能)。

10、额外的资源

Hyperledger Fabric Node SDK repo是很好的资源,里面有更深入的文档和示例代码。 您还可以在Hyperledger Rocket Chat上咨询Fabric社区和组件专家。

由于水平有限,翻译质量不太好,欢迎大家拍砖。

对应官网连接地址如下:
Hyperledger Fabric - Writing Your First Application

PS:刚入坑的小白,很多不懂,还请各位大佬多赐教,谢谢!

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

推荐阅读更多精彩内容