这是Hyperledger Fabric 官方文档里的Writing Your First Application章节。
第一次翻译,不妥之处还请网友指出,我们一起学习一起进步。
原文地址(http://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html)
这篇文档的目标是展示任务,并且为你在Hyperledger Fabric网络上编写第一个应用程序提供基准。
在最基本的层次上,区块链网络上的应用程序应该使用户能够查询(要求其包含的特定记录)或更新(向其添加记录)账本。
我们的应用程序由JavaScript组成,利用Node.js SDK与存在账本的网络进行交互。 本教程将指导您完成编写第一个应用程序所涉及的三个步骤。
1.启动一个测试Fabric区块链网络。 在Fabric中我们需要一些基本组件来查询和更新账本。 这些组件 - 对等节点,排序节点和证书颁发机构 - 充当我们网络的骨干; 我们还需要有一个用于执行几个管理命令的CLI容器。 执行一个单个脚本将下载并启动这个测试网络。
2.学习我们的app将要使用的示例智能合约的参数。 我们的智能合约包含各种功能,使我们能够以不同的方式与账本进行交互。 例如,我们可以整体或更细粒度地读取数据。
3.开发应用程序以便能够查询和更新Fabric记录。 我们提供两个示例应用程序 - 一个用于查询账本,另一个用于更新账本。 我们的应用程序将使用SDK API与网络进行交互,并最终调用这些功能。
完成本教程后,您应该了解如何使用Fabric SDK for Node.js将应用程序与智能合约一起编程,和Fabric网络上的账本进行交互。
首先,让我们启动我们的测试网络……
获得一个测试网络
请访问“先决条件”页面,并确保您的计算机上已经安装了必需的软件。
现在确定要克隆fabric-samples repo的工作目录。 执行clone命令并进入到fabcar子目录
git clone https://github.com/hyperledger/fabric-samples.git
cd fabric-samples/fabcar
fabcar这个子目录包含运行示例应用程序的脚本和应用程序代码。 在这个目录下执行ls, 您应该看到以下内容:
chaincode invoke.js network package.json query.js startFabric.sh
现在执行startFabric.sh脚本来加载网络。
这个命令将会下载并解压缩Fabric docker镜像,所以它会花费几分钟。
./startFabric.sh
为了简洁起见,我们不会深入了解这个命令的细节。这是一个快速概要:
- 加载对等节点,排序节点,证书颁发机构和命令行容器。
- 创建一个通道并把节点加入通道。
- 在对等节点的文件系统上安装智能合约(例如:链码)并且在通道上实例化上述链码;实例化启动一个链码容器。
- 调用initLedger函数在通道的账本中填充10辆独一无二的车辆。
这些操作通常由组织或对等节点的管理员完成。 脚本使用CLI来执行这些命令,然后在SDK中也有支持。 请参阅Hyperledger Fabric Node SDK repo中的示例脚本。
执行docker ps命令以显示startFabric.sh脚本启动的进程。 您可以在构建您的第一个网络部分中了解有关这些操作的细节和机制的更多信息。 这里我们将专注于应用程序。 下图提供了应用程序如何与Fabric网络交互的简单表示。
好的,现在你有一个示例网络和一些代码,让我们来看看不同的部分如何组合在一起。
应用程序如何跟网络进行交互
应用程序使用API调用智能合约(在Fabric中称为“chaincode”)。 这些智能合约托管在网络中,并通过名称和版本进行标识。 例如,我们的chaincode容器的标题为 - dev-peer0.org1.example.com-fabcar-1.0 - 其名称为fabcar,版本为1.0,并且正在运行在名为为dev-peer0.org1.example.com的对等节点上。
API可通过软件开发工具包(SDK)访问。 为了本练习的目的,我们将使用Hyperledger Fabric Node SDK,尽管还可以用Java SDK和CLI来开发应用程序。
查询账本
查询就是从分类帐中读取数据。 您可以查询单个键的值,多个键的值,或者 - 如果账本是以富数据存储格式例如JSON编写的 - 还对其执行复杂的搜索(例如查询含有某些关键字的所有资产)。
正如我们前面所说,我们的样本网络有一个活跃的链码容器和一个已经用10种不同的汽车填充的账本。 我们还在fabcar目录中有一些示例Javascript代码 - query.js,可以用于查询账本的详细信息。
在我们展示该应用程序的工作原理之前,我们需要安装SDK节点模块才能使程序运行。 从您的fabcar目录中,执行以下命令:
npm install
所有的后续命令都在fabcar这个路径下执行。
现在我们可以运行我们的JavaScript程序。 首先,我们运行我们的query.js程序以返回分类帐上所有汽车的列表。 将查询所有车辆queryAllCars的功能在应用程序中预加载,因此我们可以简单地运行程序:
node query.js
执行结果如下所示:
Query result count = 1
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辆车。 由阿德里安娜拥有的黑色特斯拉模型S,由Brad拥有的红色福特野马,由名叫Pari的人所拥有的紫罗兰菲亚特Punto等等。 账本是基于键/值对的,在我们的实现中,键的值是从CAR0到CAR9。 这将在一些时候变得特别重要。
现在让我们来看看它的实现(请你原谅这是双关语)。 使用编辑器(例如atom或visual studio)打开query.js程序。
应用程序的初始化部分定义了一些变量,如链码,通道名称和网络端点:
var options = {
wallet_path : path.join(__dirname, './network/creds'),
user_id: 'PeerAdmin',
channel_id: 'mychannel',
chaincode_id: 'fabcar',
network_url: 'grpc://localhost:7051',
这是构建我们查询的代码:
// queryCar - requires 1 argument, ex: args: ['CAR4'],
// queryAllCars - requires no arguments , ex: args: [''],
const request = {
chaincodeId: options.chaincode_id,
txId: transaction_id,
fcn: 'queryAllCars',
args: ['']
我们将chaincode_id变量定义为fabcar - 允许我们指向这个特定的链码,然后调用该链码中定义的queryAllCars函数。
在我们执行节点query.js的命令之前,这个特定的函数被调用来查询账本。但是,这不是我们能够执行的唯一函数。
要查看其它函数,请跳转到chaincode子目录,并在编辑器中打开fabcar.go。 你会看到我们有以下函数调用 - initLedger,queryCar,queryAllCars,createCar和changeCarOwner。 我们来仔细看一下queryAllCars函数,看看它如何与账本进行交互。
vfunc (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {
startKey := "CAR0"
endKey := "CAR999"
resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
}
该函数使用Fabric的shim接口的GetStateByRange函数来返回在startKey和endKey之间的账本数据。 这些键分别被定义为CAR0和CAR999。 因此,我们理论上可以创建1,000辆汽车(假设键被正确标记),并且执行一次queryAllCars会显示每一条数据。
下图展示了一个app是如何调用链码中的不同函数的过程
我们可以在这里查看我们的queryAllCars函数,以及一个名为createCar的函数,这将允许我们更新账本并最终向区块链中增加一个新的区块。 但首先,我们再做一个查询。
返回到query.js程序并编辑构造函数请求以查询特定的车辆。 我们实现这个功能通过将函数从queryAllCars更改为queryCar,并传递特定的“Key”作为args参数。 我们在这里使用CAR4。 所以我们编辑的query.js程序现在应该包含以下内容:
const request = {
chaincodeId: options.chaincode_id,
txId: transaction_id,
fcn: 'queryCar',
args: ['CAR4']
保存修改并跳转回你的fabcar目录下,执行下面的命令
node query.js
结果如下所示:
{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
所以我们从查询所有的汽车到仅查询特定的一个,Adriana的黑色特斯拉模型S.使用queryCar函数,我们可以查询任何关键字(例如CAR0),并获得与该车相对应的任何制造商,型号,颜色和所有者。
真棒。 现在,您应该很轻松的执行链码中的基本查询功能以及手动修改查询程序中的一些参数。 现在到了更新账本的时间…
更新账本
现在我们已经完成了几个账本查询并添加了一些代码,我们已经准备好更新账本。 有很多潜在的更新我们可以做,但我们作为新手先创建一辆新车。
账本更新从一个应用程序生成交易提议开始。 就像查询一样,构造了一个请求,用于识别要进行交易的通道ID,函数和特定的智能合约。 然后该程序调用channel.SendTransactionProposal API将交易提议发送给背书节点。
网络(即背书节点)返回一个提议响应,来响应应用程序构建和签名的交易请求。 该请求通过调用channel.sendTransaction API发送到排序服务。 排序服务将把事务添加到一个区块里,然后将区块传递给一个通道上的所有对等节点进行验证。 (在我们的例子中,我们只有一个背书节点。)
最后,应用程序使用eh.setPeerAddr API连接到对等节点的事件监听端口,并调用eh.registerTxEvent来注册与特定交易ID相关联的事件。 该API允许应用程序知道交易的命运(即成功提交或不成功)。 我们将其视为一种通知机制。
在交易的生命周期中,我们只了解到这里不会继续深入。 有关交易最终如何提交给账本的详细信息,请参阅交易流程文档。
我们初始化调用的目标是简单地创建一个新的资产(在这种情况下为汽车)。 我们有一个特定的JavaScript程序 - invoke.js - 我们将其用于交易。 就像查询一样,使用编辑器打开程序并跳转到构建调用的代码块:
// createCar - requires 5 args, ex: args: ['CAR11', 'Honda', 'Accord', 'Black', 'Tom'],
// changeCarOwner - requires 2 args , ex: args: ['CAR10', 'Barry'],
// send proposal to endorser
var request = {
targets: targets,
chaincodeId: options.chaincode_id,
fcn: '',
args: [''],
chainId: options.channel_id,
txId: tx_id
你会看到我们可以调用两个函数 - createCar或changeCarOwner。 让我们创建一个红色的雪佛兰伏特,并把的主人命名为尼克。 我们在账本上已经使用CAR9,所以我们在这里使用CAR10作为识别键。 更新的代码块应如下所示:
var request = {
targets: targets,
chaincodeId: options.chaincode_id,
fcn: 'createCar',
args: ['CAR10', 'Chevy', 'Volt', 'Red', 'Nick'],
chainId: options.channel_id,
txId: tx_id
保存并运行程序
node invoke.js
终端将有一些关于交易提议响应和交易ID的输出。 但是,我们关心的是这个消息:
The transaction has been committed on peer localhost:7053
对等节点发出此事件通知,我们的应用程序通过我们的eh.registerTxEvent API收到它。 所以现在,如果我们回到我们的query.js程序,并用CAR10作为参数调用queryCar函数,我们应该看到如下:
Response is {"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"}
最后,我们来调用最后一个函数changeCarOwner。 尼克感觉很慷慨,他想把他的雪佛兰Volt给一个名叫巴里的男人。 因此,我们只需编辑invoke.js即可反映以下内容:
var request = {
targets: targets,
chaincodeId: options.chaincode_id,
fcn: 'changeCarOwner',
args: ['CAR10', 'Barry'],
chainId: options.channel_id,
txId: tx_id
再次执行程序 - node invoke.js,然后最后一次运行查询应用程序。 我们仍然查询CAR10,所以我们应该看到:
Response is {"colour":"Red","make":"Chevy","model":"Volt","owner":"Barry"}
额外的资源
Hyperledger Fabric Node SDK repo是非常好的资源里面有更深入的文档和示例代码。 您还可以在Hyperledger Rocket Chat上咨询Fabric社区和组件专家。