通过第五篇 在本地搭建并跑通first-network 与 basic-network后,说明我们本地的环境是没有任何问题的。
本篇,主要介绍官方 fabric-samples中 fabcar示例项目的环境搭建,可以体验 fabric 中API的使用。
因为fabcar项目是基于node的项目。所以 本地需要node环境。
nodejs安装:菜鸟教程NodeJs安装
npm使用:菜鸟教程npm使用
进入到fabcar目录下
cd $GOPATH/src/github.com/hyperledger/fabric-samples/fabcar
执行
npm install
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"
./startFabric.sh
成功启动后docker容器已经准备完毕
逐步执行 js示例代码
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
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
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"}}]
执行
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);
既然先是不正确的数据我们就要找到什么是正确的数据。
链码源码位于
~/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方法。
在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"}
修改 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