官方原文地址 Writing Your First Application
若是对fabric网络的基本运行机制不熟悉的话,请看这里。javascript
注意:本教程是对fabric应用以及如何使用智能合约的简单介绍,对fabric应用及智能合约的详细介绍请看应用开发部分和商业票据教程。html
本教程将介绍一些示例程序以助于理解fabric应用是如何工做的。这些应用和所使用的智能合约被称为FabCar。它们是理解Hyperledger Fabric blockchain的很好的起点。你将会学习如何编写一个应用和智能合约来查询或更新帐本,以及如何使用CA生成区块链应用程序交互所须要的X.509证书。java
咱们会使用SDK(详细介绍在这里)来调用智能合约,该合约使用智能合约SDK查询和更新帐本(详细介绍在这里)。node
1. 设置开发环境:应用程序须要一个网络来进行交互,所以咱们将获得一个智能合约和应用程序所须要的基本网络。c++
2. 学习智能合约示例——FabCar:此合约是用JavaScript编写的。咱们会查看该合约,理解其中的交易,以及它是如何被应用程序用来查看和更新帐本的。git
3. 使用FabCar开发一个示例程序:该程序会使用FabCar智能合约来查询和更新帐本中的汽车资产(car assets)。咱们将深刻了解应用程序代码及其建立的交易,包括查询汽车、查询一系列汽车以及建立一辆新车。github
3、示例下载学习docker
3.一、代码下载typescript
cd $GOPATH/src/github.com/hyperledger/ git clone https://github.com/hyperledger/fabric-samples.git cd fabric-samples/fabcar
此代码放到与fabric并行目录下,没特殊要求shell
3.二、在fabcar下会有以下文件
javascript javascript-low-level startFabric.sh typescript
3.三、关闭曾经建立过的网络
注意:此部分须要你进入first-network子目录
若是你已经完成了 Building Your First Network ,说明你已经下载了fabric-samples而且启动了网络。在开始本教程以前,必须先关闭此网络:
./byfn.sh down
若是你以前运行过本教程,请使用如下命令删除全部容器(无论与fabric相关与否,都会删除):
docker rm -f $(docker ps -aq)
docker rmi -f $(docker images | grep fabcar | awk '{print $3}')
注意若是这里本身有其余的应用使用,推荐一个一个删除
若是你的环境和相关依赖没有配置好,请根据 Prerequisites 以及 Install Samples, Binaries and Docker Images 进行配置。
3.四、启动网络
注意:此部分须要你进入fabcar子目录
在fabcar目录下运行./startFabric.sh javascript
实际测试增长了javascript会启动失败,
如遇到:
ERROR: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
到上面这个问题,能够参考这里解决
再次运行,获得下面的结果:dsafdsfsfsdfsdfdsfdsfsd
这代表已经成功启动了示例网络,而且fabcar智能合约也被成功的安装和实例化了。接下来进行应用程序的安装。
3.五、安装应用程序
注意:此部分须要你进入fabcar/javascript子目录
运行下面的命令安装应用程序所需的fabric依赖(在此以前要保证已经安装了npm):
npm install
3.六、注册admin用户
注意:如下两个部分涉及与证书颁发机构的通讯。当运行程序时,经过打开新的终端shell并运行docker logs -f ca.example.com,您可能会发现流式传输CA日志很是有用。
当咱们建立网络时,一个名为admin的管理用户被建立为证书颁发机构(CA)的注册器。咱们的第一步是使用enroll.js程序为管理员生成私钥、公钥和X.509证书。此过程使用证书签名请求(CSR)(私钥和公钥首先在本地生成,而后将公钥发送到CA,CA返回编码的证书供应用程序使用。而后,这三个凭证存储在钱包中,容许咱们充当CA的管理员。)
接下来注册admin用户(此命令将会把CA管理员的凭证存储在wallet目录中):
node enrollAdmin.js
3.七、注册user用户
上一步已经注册了管理员用户,钱包中有了管理员的凭证,如今能够注册一个新用户(user1)用于查询和更新分类帐:
node registerUser.js
如今咱们就有了两个独立用户admin和user1的凭证了,后面的应用程序会用到这些凭证。
3.八、查询帐本
区块链网络中的每个节点都有一个帐本的副本,应用程序能够经过调用智能合约查询帐本,该智能合约查询帐本的最新值(世界状态)并将其返回给应用程序。世界状态是一组键值对,应用程序能够查询单个键或多个键。
如今首先运行query.js程序获取帐本上全部车辆的信息,此程序使用第二个身份(user1)来获取帐本:
node query.js
查看query.js
/* * SPDX-License-Identifier: Apache-2.0 */ 'use strict'; const { FileSystemWallet, Gateway } = require('fabric-network'); const fs = require('fs'); const path = require('path'); const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json'); const ccpJSON = fs.readFileSync(ccpPath, 'utf8'); const ccp = JSON.parse(ccpJSON); async function main() { try { // Create a new file system based wallet for managing identities. const walletPath = path.join(process.cwd(), 'wallet'); const wallet = new FileSystemWallet(walletPath); console.log(`Wallet path: ${walletPath}`); // Check to see if we've already enrolled the user. const userExists = await wallet.exists('user1'); if (!userExists) { console.log('An identity for the user "user1" does not exist in the wallet'); console.log('Run the registerUser.js application before retrying'); return; } // Create a new gateway for connecting to our peer node. const gateway = new Gateway(); await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } }); // Get the network (channel) our contract is deployed to. const network = await gateway.getNetwork('mychannel'); // Get the contract from the network. const contract = network.getContract('fabcar'); // Evaluate the specified transaction. // queryCar transaction - requires 1 argument, ex: ('queryCar', 'CAR4') // queryAllCars transaction - requires no arguments, ex: ('queryAllCars') const result = await contract.evaluateTransaction('queryAllCars'); console.log(`Transaction has been evaluated, result is: ${result.toString()}`); } catch (error) { console.error(`Failed to evaluate transaction: ${error}`); process.exit(1); } } main();
程序开始部分首先引用了fabric-network模块的FileSystemWallet
和Gateway两个关键类。这两个类用来定位user1的钱包身份,以及链接网络:
const { FileSystemWallet, Gateway } = require('fabric-network');
应用程序使用一个网关来链接网络:
const gateway = new Gateway(); await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
接下来尝试修改query.js,使其只查询CAR4:
//const result = await contract.evaluateTransaction('queryAllCars'); const result = await contract.evaluateTransaction('queryCar', 'CAR4');
具体链码能够查看:fabric-samples/chaincode查找,核心代码
/* * SPDX-License-Identifier: Apache-2.0 */ 'use strict'; const { Contract } = require('fabric-contract-api'); class FabCar extends Contract { async initLedger(ctx) { console.info('============= START : Initialize Ledger ==========='); const cars = [ { color: 'blue', make: 'Toyota', model: 'Prius', owner: 'Tomoko', }, { color: 'red', make: 'Ford', model: 'Mustang', owner: 'Brad', }, { color: 'green', make: 'Hyundai', model: 'Tucson', owner: 'Jin Soo', }, { color: 'yellow', make: 'Volkswagen', model: 'Passat', owner: 'Max', }, { color: 'black', make: 'Tesla', model: 'S', owner: 'Adriana', }, { color: 'purple', make: 'Peugeot', model: '205', owner: 'Michel', }, { color: 'white', make: 'Chery', model: 'S22L', owner: 'Aarav', }, { color: 'violet', make: 'Fiat', model: 'Punto', owner: 'Pari', }, { color: 'indigo', make: 'Tata', model: 'Nano', owner: 'Valeria', }, { color: 'brown', make: 'Holden', model: 'Barina', owner: 'Shotaro', }, ]; for (let i = 0; i < cars.length; i++) { cars[i].docType = 'car'; await ctx.stub.putState('CAR' + i, Buffer.from(JSON.stringify(cars[i]))); console.info('Added <--> ', cars[i]); } console.info('============= END : Initialize Ledger ==========='); } async queryCar(ctx, carNumber) { const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode state if (!carAsBytes || carAsBytes.length === 0) { throw new Error(`${carNumber} does not exist`); } console.log(carAsBytes.toString()); return carAsBytes.toString(); } async createCar(ctx, carNumber, make, model, color, owner) { console.info('============= START : Create Car ==========='); const car = { color, docType: 'car', make, model, owner, }; await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car))); console.info('============= END : Create Car ==========='); } async queryAllCars(ctx) { const startKey = 'CAR0'; const endKey = 'CAR999'; const iterator = await ctx.stub.getStateByRange(startKey, endKey); const allResults = []; while (true) { const res = await iterator.next(); if (res.value && res.value.value.toString()) { console.log(res.value.value.toString('utf8')); const Key = res.value.key; let Record; try { Record = JSON.parse(res.value.value.toString('utf8')); } catch (err) { console.log(err); Record = res.value.value.toString('utf8'); } allResults.push({ Key, Record }); } if (res.done) { console.log('end of data'); await iterator.close(); console.info(allResults); return JSON.stringify(allResults); } } } async changeCarOwner(ctx, carNumber, newOwner) { console.info('============= START : changeCarOwner ==========='); const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode state if (!carAsBytes || carAsBytes.length === 0) { throw new Error(`${carNumber} does not exist`); } const car = JSON.parse(carAsBytes.toString()); car.owner = newOwner; await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car))); console.info('============= END : changeCarOwner ==========='); } } module.exports = FabCar;
再次运行query.js:
$ node query-car4.js
Wallet path: /Users/lihongxu6/work/mygo/src/github.com/hyperledger/fabric-samples/fabcar/javascript/wallet
Transaction has been evaluated, result is: {"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
如今,大概已经对查询交易有了大概的了解,接下来看看如何更新帐本。
咱们能够作不少方面的更新操做,但先从建立一辆新车开始吧。
从应用程序的角度来看,更新帐本是很简单的。应用先提交一个交易到区块链网络,而后当这个交易被证实有效后,应用会收到一个交易成功的通知。这个过程涉及到共识机制:
上图显示了更新帐本所涉及到的主要组件。区块链网络包含多个peer,每一个peer都维护一份帐本副本,而且选择性的维护一个智能合约副本,除此以外,网络还包括一个排序服务。排序服务可以建立一个包含链接到此网络的不一样应用所提交的通过排序的交易的区块。
咱们使用invoke.js程序来实现建立一辆新车的更新操做:如下是在invoke中的一段代码:
await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
运行
$ node invoke.js Wallet path: /Users/lihongxu6/work/mygo/src/github.com/hyperledger/fabric-samples/fabcar/javascript/wallet 2019-04-03T10:26:48.661Z - info: [TransactionEventHandler]: _strategySuccess: strategy success for transaction "5a9563b1397339de4de191aa3c6e52371033811b9aa3d3435bc429b2afabe54f" Transaction has been submitted
注意一下,invoke.js程序与区块链交互使用的是submitTransaction API而不是evaluateTransaction。
submitTransaction比evaluateTransaction复杂的多。SDK会将submitTransaction提案发送给每个须要的组织中的peer,而不是只和单个peer交互。全部这些接收提案的peer将会执行提案要求执行的智能合约生成交易响应,并对交易响应进行签名后发回给SDK。SDK将收集到的全部通过签名的交易响应合并到一个新的交易中,而后将其发送给orderer。orderer未来自各个应用的交易进行收集和排序后,将这些交易放到区块中。而后将这个新区块(每一个交易都是通过证实合法的)分发给网络中的全部peer节点。最后,通知SDK将控制权交回给应用程序。
上一段很重要,涉及到不少工做,而这些工做都是由submitTransaction完成的。应用程序、智能合约、peers、ordering service协同工做来保证帐本的一致性的过程叫作共识过程,共识机制的详细介绍看这里。
经过已下查看便可【注意链码调用方法】
node query.js
查看结果中会有新加入的记录
如今,假设Tom很是慷慨,想把Honda Accord车送给Dave,为了实现这个过程,须要修改invoke.js程序,
// await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom'); await contract.submitTransaction('changeCarOwner', 'CAR12', 'Dave');
再次执行invoke.js程序(因为网络缘由,可能会因为链接超时而报错,多运行几回便可):
$ node invoke.js Wallet path: /Users/lihongxu6/work/mygo/src/github.com/hyperledger/fabric-samples/fabcar/javascript/wallet 2019-04-03T10:35:21.125Z - info: [TransactionEventHandler]: _strategySuccess: strategy success for transaction "d64761e0880c70b4578b53e467e05cfa8696cfb2791bce5d3062bc1196894359" Transaction has been submitted
再次执行query.js查看owner值是否为Dave:
能够看到,全部者确实变为了Dave。
至此,就成功完成了fabric1.4官方文档中的writing your first application部分。这一部分比较简单,更深刻的请看下面:
As we said in the introduction, we have a whole section on Developing Applications that includes in-depth information on smart contracts, process and data design, a tutorial using a more in-depth Commercial Paper tutorial and a large amount of other material relating to the development of applications.
如下是1.1版本的示例,chaincode在根目录下
三、开启网络配置[注意docker版本17.06,在1.12.6版本没有-e命令]
./startFabric.sh
四、查询一个帐本
安装node
yum install gcc-c++
npm install
查询[注意配置query.js中的ip地址]
node query.js
展现以下数据
Create a client and set the wallet location Set wallet path, and associate user PeerAdmin with application Check user is enrolled, and set a query URL in the network Make query Assigning transaction_id: f2f45cb045d6290d199e1b2d4eb3b60b1e9cafeff8d09e2b7683dd8578492be7 returned from query 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"}}]
五、分析query.js
1》初始化参数,包含了用户ID,信道,链码,网络链接入口
var options = { wallet_path: path.join(__dirname, './creds'), user_id: 'PeerAdmin', channel_id: 'mychannel', chaincode_id: 'fabcar', network_url: 'grpc://localhost:7051', };
2》查询代码
var transaction_id = client.newTransactionID(); // 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: [''] }; return channel.queryByChaincode(request);
这里设置了链码ID,交易ID,以及调用的链码的方法fcn,方法参数args等
3》链码:/chaincode/fabcar/目录下fabcar.go
此文件匹配上文的链码ID,包含了以下方法:initLedger
, queryCar
,queryAllCars
, createCar
and changeCarOwner
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { startKey := "CAR0" endKey := "CAR999" resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
此处就是查询范围内的数据。
查看全部方法
六、测试
cp query.js query1.js
vim query1.js
修改内部访问链码方法
const request = { chaincodeId: options.chaincode_id, txId: transaction_id, fcn: 'queryCar', args: ['CAR4'] };
执行:node query1.js
Create a client and set the wallet location Set wallet path, and associate user PeerAdmin with application Check user is enrolled, and set a query URL in the network Make query Assigning transaction_id: ca88dc3b60f4df009a709f2f5ee5ad3b54f43d03a7e0b931042e2797f70c795d returned from query Query result count = 1 Response is {"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}
七、更新帐本数据
使用fabcar目录下的invoke.js
修改,中的 fcn,以及args等参数
var request = { targets: targets, chaincodeId: options.chaincode_id, fcn: '', args: [''], chainId: options.channel_id, txId: tx_id
以下
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
成功后会有
The transaction has been committed on peer localhost:7053
执行
cp query1.js query2.js vim query2.js
将query2.js中查询条件参数,变为CAR10便可
Response is {"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"}
ok,能够继续调试其余方法。