概述
本文介绍如何设置Hyperledger Fabric Network(HLF)v2.2、部署链码、调用和查询链码。我们首先给予了一个高层次的过程演练,然后使用HLF示例中提供的测试网络来演示整个过程。Test Network中提供的脚本使得区块链网络和Fabric CA服务器的设置变得更加容易。
基本步骤
- 使用Fabric CA服务器从组织和订购者(Org1、Org2和订购者)生成crypto-material。
- 产生通道相关组件,例如系统通道块、通道事务、通道创世块等)。
- 创建peer anchor。
- 创建通道。
- 加入通道。
- 在通道中部署chaincode。
- 调用或查询链码。
所需软件环境
- Golang
- Docker
- Nodejs
构建网络
在这里,我们首先下载HLF,使用其提供的测试网络。我们的网络将由两个组织(每个组织一个peer)、一个orderer节点、三个CA服务器(每个组织一个,以及orderer一个)、两个状态数据库couchdb实例(每个peer一个)组成。
1.下载fabric并安装相关镜像。
curl -sSL https://bit.ly/2ysbOFE | bash -s -- 2.3.0 1.4.9
2.设置环境变量
cd fabric-samples && export PATH=$PWD/bin:$PATH
3.完成步骤2后,转到test-network文件夹。
cd test-network
4.可以运行命令检查所有可用选项: ./network.sh help
5.使用Fabric CA服务器、CouchDB启动网络并创建通道 mychannel。
-ca:使用证书颁发机构生成网络加密材料-c:要创建的频道名称(默认为“mychannel”)-s:要部署的对等状态数据库:goleveldb(默认)或couchdbup-显示结构订购者和对等节点。未创建通道。up createChannel:启动具有一个通道的结构网络。createChannel:在网络创建后创建并加入频道
./network.sh up createChannel -ca -c mychannel -s couchdb
如果一切顺利的话,最后你会看到这样的画面。
6.下一步部署chaincode。注意:在示例中有多个chaincode。
-ccn:链代码名称。此标志可用于将其中一个资产传输样本部署到通道。示例选项:basic (default), ledger, private, sbe, secured-ccl:要部署的链码的编程语言:go(default),java,javascript,typescript-verbose:Verbose模式
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-javascript/ -ccl javascript -verbose
一旦chaincode部署成功,您将获得如下输出
7.现在,链码部署已经完成,我们将调用和查询链码。我们将使用nodejs SDK与区块链网络进行交互。
cd ../asset-transfer-basic/application-javascript && npm install
node app.js
此脚本将用户 appUser 注册到FileSystemWallet中,并将调用 InitLedger 函数,该函数将在分类账中创建6条记录。在此之后,它调用函数 GetAllAssets *并且此函数返回分类帐中存储的所有记录,之后调用 CreateAsset这将在分类账中添加新记录。
具体代码如下:
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const channelName = 'mychannel';
const chaincodeName = 'basic';
const mspOrg1 = 'Org1MSP';
const walletPath = path.join(__dirname, 'wallet');
const org1UserId = 'appUser';
function prettyJSONString(inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
// pre-requisites:
// - fabric-sample two organization test-network setup with two peers, ordering service,
// and 2 certificate authorities
// ===> from directory /fabric-samples/test-network
// ./network.sh up createChannel -ca
// - Use any of the asset-transfer-basic chaincodes deployed on the channel "mychannel"
// with the chaincode name of "basic". The following deploy command will package,
// install, approve, and commit the javascript chaincode, all the actions it takes
// to deploy a chaincode to a channel.
// ===> from directory /fabric-samples/test-network
// ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-javascript/ -ccl javascript
// - Be sure that node.js is installed
// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript
// node -v
// - npm installed code dependencies
// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript
// npm install
// - to run this test application
// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript
// node app.js
// NOTE: If you see kind an error like these:
/*
2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied
******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied
OR
Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]]
******** FAILED to run the application: Error: Identity not found in wallet: appUser
*/
// Delete the /fabric-samples/asset-transfer-basic/application-javascript/wallet directory
// and retry this application.
//
// The certificate authority must have been restarted and the saved certificates for the
// admin and application user are not valid. Deleting the wallet store will force these to be reset
// with the new certificate authority.
//
/**
* A test application to show basic queries operations with any of the asset-transfer-basic chaincodes
* -- How to submit a transaction
* -- How to query and check the results
*
* To see the SDK workings, try setting the logging to show on the console before running
* export HFC_LOGGING='{"debug":"console"}'
*/
async function main() {
try {
// build an in memory object with the network configuration (also known as a connection profile)
const ccp = buildCCPOrg1();
// build an instance of the fabric ca services client based on
// the information in the network configuration
const caClient = buildCAClient(FabricCAServices, ccp, 'ca.org1.example.com');
// setup the wallet to hold the credentials of the application user
const wallet = await buildWallet(Wallets, walletPath);
// in a real application this would be done on an administrative flow, and only once
await enrollAdmin(caClient, wallet, mspOrg1);
// in a real application this would be done only when a new user was required to be added
// and would be part of an administrative flow
await registerAndEnrollUser(caClient, wallet, mspOrg1, org1UserId, 'org1.department1');
// Create a new gateway instance for interacting with the fabric network.
// In a real application this would be done as the backend server session is setup for
// a user that has been verified.
const gateway = new Gateway();
try {
// setup the gateway instance
// The user will now be able to create connections to the fabric network and be able to
// submit transactions and query. All transactions submitted by this gateway will be
// signed by this user using the credentials stored in the wallet.
await gateway.connect(ccp, {
wallet,
identity: org1UserId,
discovery: { enabled: true, asLocalhost: true } // using asLocalhost as this gateway is using a fabric network deployed locally
});
// Build a network instance based on the channel where the smart contract is deployed
const network = await gateway.getNetwork(channelName);
// Get the contract from the network.
const contract = network.getContract(chaincodeName);
// Initialize a set of asset data on the channel using the chaincode 'InitLedger' function.
// This type of transaction would only be run once by an application the first time it was started after it
// deployed the first time. Any updates to the chaincode deployed later would likely not need to run
// an "init" type function.
console.log('\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger');
await contract.submitTransaction('InitLedger');
console.log('*** Result: committed');
// Let's try a query type operation (function).
// This will be sent to just one peer and the results will be shown.
console.log('\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger');
let result = await contract.evaluateTransaction('GetAllAssets');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Now let's try to submit a transaction.
// This will be sent to both peers and if both peers endorse the transaction, the endorsed proposal will be sent
// to the orderer to be committed by each of the peer's to the channel ledger.
console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments');
result = await contract.submitTransaction('CreateAsset', 'asset13', 'yellow', '5', 'Tom', '1300');
console.log('*** Result: committed');
if (`${result}` !== '') {
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
}
console.log('\n--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID');
result = await contract.evaluateTransaction('ReadAsset', 'asset13');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Evaluate Transaction: AssetExists, function returns "true" if an asset with given assetID exist');
result = await contract.evaluateTransaction('AssetExists', 'asset1');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Submit Transaction: UpdateAsset asset1, change the appraisedValue to 350');
await contract.submitTransaction('UpdateAsset', 'asset1', 'blue', '5', 'Tomoko', '350');
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes');
result = await contract.evaluateTransaction('ReadAsset', 'asset1');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
try {
// How about we try a transactions where the executing chaincode throws an error
// Notice how the submitTransaction will throw an error containing the error thrown by the chaincode
console.log('\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error');
await contract.submitTransaction('UpdateAsset', 'asset70', 'blue', '5', 'Tomoko', '300');
console.log('******** FAILED to return an error');
} catch (error) {
console.log(`*** Successfully caught the error: \n ${error}`);
}
console.log('\n--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom');
await contract.submitTransaction('TransferAsset', 'asset1', 'Tom');
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes');
result = await contract.evaluateTransaction('ReadAsset', 'asset1');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
} finally {
// Disconnect from the gateway when the application is closing
// This will close all connections to the network
gateway.disconnect();
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();
我们可以通过fauxton UI来查询结果。
peer0.org1:http://localhost:7984/_utils/#
peer0.org2: http://localhost:7984/_utils/#