七、区块链学习-Hyperledger Fabric (基于release-1.0)官方示例Fabcar 体验Nodejs SDK使用

七、区块链学习-Hyperledger Fabric 官方示例Fabcar 基于release-1.0 体验Nodejs SDK使用

  • 1. 概述
  • 2. 环境准备
  • 3. 安装fabric的node依赖
  • 4. 查看Fabcar项目中的启动脚本
  • 5. 安装startFabric.sh脚本中的提示 逐步搭建环境
    • 5.1 在fabcar的目录下执行启动脚本
    • 5.2 执行 enrollAdmin
    • 5.3 执行registerUser
    • 5.4 执行query
    • 5.5 尝试调用invoke
    • 5.5 查看fabric.go链码
    • 5.6 执行其他函数
      • 5.6.1 执行queryCar 方法
      • 5.6.2 执行createCar方法

1. 概述

通过第五篇 在本地搭建并跑通first-network 与 basic-network后,说明我们本地的环境是没有任何问题的。
本篇,主要介绍官方 fabric-samples中 fabcar示例项目的环境搭建,可以体验 fabric 中API的使用。

2. 环境准备

因为fabcar项目是基于node的项目。所以 本地需要node环境。
nodejs安装:菜鸟教程NodeJs安装
npm使用:菜鸟教程npm使用

3. 安装fabric的node依赖

进入到fabcar目录下

cd $GOPATH/src/github.com/hyperledger/fabric-samples/fabcar

执行

npm install

4. 查看Fabcar项目中的启动脚本

startFabric.sh

#!/bin/bash
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
# Exit on first error
set -e

# don't rewrite paths for Windows Git Bash users
export MSYS_NO_PATHCONV=1

starttime=$(date +%s)

# launch network; create channel and join peer to channel
# 这里找到了第六篇介绍的basic-network 并执行了start脚本,说明这个工程是基于basic-network的网络环境的
cd ../basic-network
./start.sh

# Now launch the CLI container in order to install, instantiate chaincode
# 现在启动cli 容器 并按照链码
# and prime the ledger with our 10 cars
# 并用10个车的信息作为基础数据写入到账本中
docker-compose -f ./docker-compose.yml up -d cli

# 以Admin的MSP 进入cli容器 并安装fabcar链码
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/[email protected]/msp" cli peer chaincode install -n fabcar -v 1.0 -p github.com/fabcar

# 实例化链码
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/[email protected]/msp" cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fabcar -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
sleep 10
# 初始化账本信息
docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/[email protected]/msp" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{"function":"initLedger","Args":[""]}'

# 到这里环境准备完毕
printf "\nTotal setup execution time : $(($(date +%s) - starttime)) secs ...\n\n\n"
# 执行npm install 安装依赖
printf "Start by installing required packages run 'npm install'\n"
# 执行node enrollAdmin.js 然后执行node registerUser.js
printf "Then run 'node enrollAdmin.js', then 'node registerUser'\n\n"
# 执行node invoke.js
# “node invoke.js”将失败,直到用有效参数更新它为止
printf "The 'node invoke.js' will fail until it has been updated with valid arguments\n"
# 执行node query.js 可以看到当前账本记录的信息
printf "The 'node query.js' may be run at anytime once the user has been registered\n\n"

5. 安装startFabric.sh脚本中的提示 逐步搭建环境

5.1 在fabcar的目录下执行启动脚本

./startFabric.sh

成功启动后docker容器已经准备完毕
逐步执行 js示例代码

5.2 执行 enrollAdmin

node enrollAdmin.js

输出

 Store path:/Users/lh0811/go/src/github.com/hyperledger/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":"924a2b8f45b43497e012908f9efb2094fb1110b706242ee85f04ea852a7e2d27","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIIB8DCCAZegAwIBAgIUdcF8zVQirr0tldKMJzeWsX2HnW8wCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjAwMjE1MDIyODAwWhcNMjEwMjE0MDIy\nODAwWjAQMQ4wDAYDVQQDEwVhZG1pbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA\nBIcMRTaGb4Ol3RGoD3YbAK2qB6cYkN6Gmq8LdwAn7W4rjM8hu+yv8OCFMEyiITmw\nXDogYY+tQKSFRGTQYHvBY+KjbDBqMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8E\nAjAAMB0GA1UdDgQWBBQ6ECHcYOg/8Q3XLqmDb4YPepECLTArBgNVHSMEJDAigCBC\nOaoNzXba7ri6DNpwGFHRRQTTGq0bLd3brGpXNl5JfDAKBggqhkjOPQQDAgNHADBE\nAiAsGB4rn6TFZxhhLeJlDcs2tjlhiK0h7cGU6dCStDzbwgIgISznuHTVJmPjnjZn\nN2tAVhx8/JbxDeBSQV95R4hKGaU=\n-----END CERTIFICATE-----\n"}}}

第一句日志 表名数据存储的路径为

Store path:$GOPATH/src/github.com/hyperledger/fabric-samples/fabcar/hfc-key-store

5.3 执行registerUser

node registerUser.js

可能会出现错误提示

 Store path:/Users/lh0811/go/src/github.com/hyperledger/fabric-samples/fabcar/hfc-key-store
Successfully loaded admin from persistence
Failed to register: Error: fabric-ca request register failed with errors [[{"code":0,"message":"No identity type provided. Please provide identity type"}]]

解决方案:
修改registerUser.js第56行 添加角色信息

// return fabric_ca_client.register({enrollmentID: 'user1', affiliation: 'org1.department1'}, admin_user);
return fabric_ca_client.register({enrollmentID: 'user1', affiliation: 'org1.department1',role: 'client'}, admin_user);

成功执行后输出

 Store path:/Users/lh0811/go/src/github.com/hyperledger/fabric-samples/fabcar/hfc-key-store
Successfully loaded admin from persistence
Successfully registered user1 - secret:ujtwLZpQpHVU
Successfully enrolled member user "user1" 
User1 was successfully registered and enrolled and is ready to intreact with the fabric network

5.4 执行query

node query.js

输出

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"}}]

5.5 尝试调用invoke

执行

ndoe invoke.js

输出日志

Transaction proposal was bad
Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...
Failed to invoke successfully :: Error: Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...

之前在启动脚本中写到

# “node invoke.js”将失败,直到用有效参数更新它为止
printf "The 'node invoke.js' will fail until it has been updated with valid 

查看 invoke.js的代码

'use strict';
/*
* Copyright IBM Corp All Rights Reserved
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
 * Chaincode Invoke
 */

var Fabric_Client = require('fabric-client');
var path = require('path');
var util = require('util');
var os = require('os');

//
var fabric_client = new Fabric_Client();

// setup the fabric network
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpc://localhost:7051');
channel.addPeer(peer);
var order = fabric_client.newOrderer('grpc://localhost:7050')
channel.addOrderer(order);

//
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log('Store path:'+store_path);
var tx_id = null;

// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
	// assign the store to the fabric client
	fabric_client.setStateStore(state_store);
	var crypto_suite = Fabric_Client.newCryptoSuite();
	// use the same location for the state store (where the users' certificate are kept)
	// and the crypto store (where the users' keys are kept)
	var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
	crypto_suite.setCryptoKeyStore(crypto_store);
	fabric_client.setCryptoSuite(crypto_suite);

	// get the enrolled user from persistence, this user will sign all requests
	return fabric_client.getUserContext('user1', true);
}).then((user_from_store) => {
	if (user_from_store && user_from_store.isEnrolled()) {
		console.log('Successfully loaded user1 from persistence');
		member_user = user_from_store;
	} else {
		throw new Error('Failed to get user1.... run registerUser.js');
	}

	// get a transaction id object based on the current user assigned to fabric client
	tx_id = fabric_client.newTransactionID();
	console.log("Assigning transaction_id: ", tx_id._transaction_id);

	// 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
	};

	// send the transaction proposal to the peers
	return channel.sendTransactionProposal(request);
}).then((results) => {
	var proposalResponses = results[0];
	var proposal = results[1];
	let isProposalGood = false;
	if (proposalResponses && proposalResponses[0].response &&
		proposalResponses[0].response.status === 200) {
			isProposalGood = true;
			console.log('Transaction proposal was good');
		} else {
			console.error('Transaction proposal was bad');
		}
	if (isProposalGood) {
		console.log(util.format(
			'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s"',
			proposalResponses[0].response.status, proposalResponses[0].response.message));

		// build up the request for the orderer to have the transaction committed
		var request = {
			proposalResponses: proposalResponses,
			proposal: proposal
		};

		// set the transaction listener and set a timeout of 30 sec
		// if the transaction did not get committed within the timeout period,
		// report a TIMEOUT status
		var transaction_id_string = tx_id.getTransactionID(); //Get the transaction ID string to be used by the event processing
		var promises = [];

		var sendPromise = channel.sendTransaction(request);
		promises.push(sendPromise); //we want the send transaction first, so that we know where to check status

		// get an eventhub once the fabric client has a user assigned. The user
		// is required bacause the event registration must be signed
		let event_hub = fabric_client.newEventHub();
		event_hub.setPeerAddr('grpc://localhost:7053');

		// using resolve the promise so that result status may be processed
		// under the then clause rather than having the catch clause process
		// the status
		let txPromise = new Promise((resolve, reject) => {
			let handle = setTimeout(() => {
				event_hub.disconnect();
				resolve({event_status : 'TIMEOUT'}); //we could use reject(new Error('Trnasaction did not complete within 30 seconds'));
			}, 3000);
			event_hub.connect();
			event_hub.registerTxEvent(transaction_id_string, (tx, code) => {
				// this is the callback for transaction event status
				// first some clean up of event listener
				clearTimeout(handle);
				event_hub.unregisterTxEvent(transaction_id_string);
				event_hub.disconnect();

				// now let the application know what happened
				var return_status = {event_status : code, tx_id : transaction_id_string};
				if (code !== 'VALID') {
					console.error('The transaction was invalid, code = ' + code);
					resolve(return_status); // we could use reject(new Error('Problem with the tranaction, event status ::'+code));
				} else {
					console.log('The transaction has been committed on peer ' + event_hub._ep._endpoint.addr);
					resolve(return_status);
				}
			}, (err) => {
				//this is the callback if something goes wrong with the event registration or processing
				reject(new Error('There was a problem with the eventhub ::'+err));
			});
		});
		promises.push(txPromise);

		return Promise.all(promises);
	} else {
		console.error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...');
		throw new Error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...');
	}
}).then((results) => {
	console.log('Send transaction promise and event listener promise have completed');
	// check the results in the order the promises were added to the promise all list
	if (results && results[0] && results[0].status === 'SUCCESS') {
		console.log('Successfully sent transaction to the orderer.');
	} else {
		console.error('Failed to order the transaction. Error code: ' + response.status);
	}

	if(results && results[1] && results[1].event_status === 'VALID') {
		console.log('Successfully committed the change to the ledger by the peer');
	} else {
		console.log('Transaction failed to be committed to the ledger due to ::'+results[1].event_status);
	}
}).catch((err) => {
	console.error('Failed to invoke successfully :: ' + err);
});

上述代码中第46行
当挂载数据存储地址之后,会构建一个 request参数

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

当做节点的交易数据传递

// send the transaction proposal to the peers
return channel.sendTransactionProposal(request);

既然先是不正确的数据我们就要找到什么是正确的数据。

5.5 查看fabric.go链码

链码源码位于

~/fabric-samples/chaincode/fabcar/fabcar.go

查看源码 可以看到car的结构体

type Car struct {
	Make   string `json:"make"`
	Model  string `json:"model"`
	Colour string `json:"colour"`
	Owner  string `json:"owner"`
}

可以看到其中的公开方法

# 初始化账本
func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response

# 查询单个汽车
func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response

# 创建一个汽车
func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response

# 查询全部汽车
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response

# 更改汽车的拥有者
func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response

查看query.js 的源码

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

// send the query proposal to the peer
return channel.queryByChaincode(request);

从源码中可以看出 其实是调用了queryAllCars方法。

5.6 执行其他函数

5.6.1 执行queryCar 方法

在initLedger函数中可以看到

for i < len(cars) {
		fmt.Println("i is ", i)
		carAsBytes, _ := json.Marshal(cars[i])
		APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
		fmt.Println("Added", cars[i])
		i = i + 1
	}

所以 其实 每个CAR的state是 CAR+index的组合。
所以查找是 也要拼接一个state

复制一个query.js 命名为query2.js
修改代码query2.js 54行

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

执行

node query2.js

输出

Store path:/Users/lh0811/go/src/github.com/hyperledger/fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is  {"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}

5.6.2 执行createCar方法

修改 query2.js 第54行

var request = {
		//targets: let default to the peer assigned to the client
		chaincodeId: 'fabcar',
		fcn: 'createCar',
		args: ['CAR12', 'Honda', 'Accord', 'Black', 'Tom'],
		chainId: 'mychannel',
		txId: tx_id
	};

执行

node query2.js

在查询全部的car

node query.js

在输出中就可以找到新创建的CAR12

你可能感兴趣的:(区块链-Hyperledger,Fabric)