从上一篇内容我们可以看到,执行node query.js
之后,返回了 CAR0~CAR9
的信息。那这整个流程都做了些什么操作?
解析 query.js
我们用编辑器打开query.js
'use strict';
/*
* Copyright IBM Corp All Rights Reserved
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Chaincode query
*/
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
// 创建 channnel、 peer
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpc://192.168.1.135:7051');
// 将 peer 加入到 channel
channel.addPeer(peer);
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');
}
// queryCar chaincode function - requires 1 argument, ex: args: ['CAR4'],
// queryAllCars chaincode function - requires no arguments , ex: args: [''],
// 构建调用 chaincode 的方法及参数。
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);
}).then((query_responses) => {
// 返回的数据
console.log("Query has completed, checking results");
// query_responses could have more than one results if there multiple peers were used as targets
if (query_responses && query_responses.length == 1) {
if (query_responses[0] instanceof Error) {
console.error("error from query = ", query_responses[0]);
} else {
console.log("Response is ", query_responses[0].toString());
}
} else {
console.log("No payloads were returned from query");
}
}).catch((err) => {
console.error('Failed to query successfully :: ' + err);
});
这里面我们的关注点在于如何构建 chaincode 调用函数及其参数。
const request = {
//targets : --- letting this default to the peers assigned to the channel
chaincodeId: 'fabcar', // chaincode 名字
fcn: 'queryAllCars', // 调用的chaincode 函数
args: [''] // 传递的参数
};
// 向 peer 发送查询请求
return channel.queryByChaincode(request);
ok。看到这的时候,我就有个疑问了。为啥函数的名字是queryAllCars
。究竟还有多少个函数可以调用查询?
那我们只能去查看 chaincode 的代码了。要想查看代码,我们就要先找到代码在哪个位置。很是明确。
查找 chaincode 的位置
- 查看
startFabric.sh
文件。
CC_SRC_PATH=github.com/fabcar/go
if [ "$LANGUAGE" = "node" -o "$LANGUAGE" = "NODE" ]; then
CC_SRC_PATH=/opt/gopath/src/github.com/fabcar/node
fi
发现chaincode 位于github.com/fabcar/go
(这里显示的是docker 容器内的位置,在 $GOPATH/src
目录下)
我们来确认一下,是不是在这里。
# docker exec -it cli /bin/bash 这个命令可以交互式的进入 cli 容器
VirtualBox:~/code/fabric/src/fabric-samples/fabcar$ docker exec -it cli /bin/bash
# 跳转到 $GOPATH/src/github.com/fabcar/go/ 目录下
root@9249c48132cd:/opt/gopath/src/github.com/hyperledger/fabric/peer# cd $GOPATH/src/github.com/fabcar/go/
# 查看目录内容
root@9249c48132cd:/opt/gopath/src/github.com/fabcar/go# ls
fabcar.go
好的,我们通过上面的命令,确定了chaincode
位于 cli
容器的 $GOPATH/src/github.com/fabcar/go/
目录下。
可是在容器外部,我的代码存放在哪里呢?我总不能在 cli
容器内安装一个 vim
来查看代码吧?代码少还好说,代码多了的话。还要装一些乱七八糟的工具,想想都累。
- 查看启动的网络
在startFabric.sh
脚本内,我们可以看到下面的代码。
# launch network; create channel and join peer to channel
cd ../basic-network // 进入basic-network 目录
./start.sh // 执行start.sh 脚本
我们进入该目录结构下找找yaml
文件中,启动 cli
容器时的配置。
我们打开../basic-network
目录下的start.sh
脚本,可以查看到如下内容:
docker-compose -f docker-compose.yml down
docker-compose -f docker-compose.yml up -d ca.example.com orderer.example.com peer0.org1.example.com couchdb
我们可以发现是通过docker-compose.yaml
文件来启动的网络。
我们再打开docker-compose.yaml
文件看看,在里面找找cli
的配置项:
cli:
container_name: cli // 启动容器的名字
image: hyperledger/fabric-tools // 依赖的镜像文件
tty: true
environment: // 指定环境变量
- GOPATH=/opt/gopath
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_LOGGING_LEVEL=info
- CORE_PEER_ID=cli
- CORE_PEER_ADDRESS=peer0.org1.example.com:7051
- CORE_PEER_LOCALMSPID=Org1MSP
- CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
- CORE_CHAINCODE_KEEPALIVE=10
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer //工作目录
command: /bin/bash
volumes: // 挂在的目录
- /var/run/:/host/var/run/
- ./../chaincode/:/opt/gopath/src/github.com/ // 啊哈,原来是把./../chaincode 目录挂在到了 cli 容器的github.com 目录下。
- ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
networks:
- basic
好的,原来是把./../chaincode 目录挂在到了 cli 容器的github.com 目录下。我们去这个目录下找找,到底在不在这。
VirtualBox:~/code/fabric/src/fabric-samples/basic-network$ cd ./../chaincode
VirtualBox:~/code/fabric/src/fabric-samples/chaincode$ ls
abac chaincode_example02 fabcar hyperledger marbles02 marbles02_private sacc
VirtualBox:~/code/fabric/src/fabric-samples/chaincode$ cd fabcar/go/
VirtualBox:~/code/fabric/src/fabric-samples/chaincode/fabcar/go$ ls
fabcar.go
我的天,终于知道到fabcar.go
文件了。下面我们打开这个文件看看都写了些啥。
解析 chaincode
我们打开fabcar.go
文件可以看到如下内容。将代码拆分讲解:
如果想看完整的内容,可以去github查看。https://github.com/hyperledger/fabric-samples/blob/release-1.2/chaincode/fabcar/go/fabcar.go
// 定义了一个SmartContract 结构
// Define the Smart Contract structure
type SmartContract struct {
}
// Define the car structure, with 4 properties. Structure tags are used by encoding/json library
// 定义了一个 Car 结构,里面包含了四个字符串字段。
type Car struct {
Make string `json:"make"`
Model string `json:"model"`
Colour string `json:"colour"`
Owner string `json:"owner"`
}
先不考虑内容,先看下整体的实现。SmartContract
实现了Init
和Invoke
这两个方法。那么就实现了chaincode
的接口。反过来说,你自己写的chaincode
一定要包含这两个方法,来实现链代码的接口。
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
}
/*
* The Invoke method is called as a result of an application request to run the Smart Contract "fabcar"
* The calling application program has also specified the particular smart contract function to be called, with arguments
*/
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
}
main
函数执行shim.Start()
函数来启动链码。
func main() {
// Create a new Smart Contract
err := shim.Start(new(SmartContract))
if err != nil {
fmt.Printf("Error creating new Smart Contract: %s", err)
}
}
这就是大体的框架,我们再来细看代码的内容。
Init
方法,是在执行链码实例化instantiate
和更新upgrade
操作的时候才会调用的。Invoke
方法,是正常函数调用时的路由操作。
在查看query.js
脚本时,我们看到其调用了queryAllCars
函数。
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
// Retrieve the requested Smart Contract function and arguments
function, args := APIstub.GetFunctionAndParameters()
// Route to the appropriate handler function to interact with the ledger appropriately
if function == "queryCar" {
return s.queryCar(APIstub, args)
} else if function == "initLedger" {
return s.initLedger(APIstub)
} else if function == "createCar" {
return s.createCar(APIstub, args)
} else if function == "queryAllCars" {
return s.queryAllCars(APIstub)
} else if function == "changeCarOwner" {
return s.changeCarOwner(APIstub, args)
}
return shim.Error("Invalid Smart Contract function name.")
}
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {
// 索引的范围 CAR 0~ CAR99
startKey := "CAR0"
endKey := "CAR999"
// 调用 APIstub.GetStateByRange 方法来获取内容
resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
if err != nil {
return shim.Error(err.Error())
}
// 使用完之后,调用 Close 方法来关闭迭代器
defer resultsIterator.Close()
// buffer is a JSON array containing QueryResults
var buffer bytes.Buffer
buffer.WriteString("[")
// 将内容构建为数组。并调用`shim.Success`返回内容
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"Key\":")
buffer.WriteString("\"")
buffer.WriteString(queryResponse.Key)
buffer.WriteString("\"")
buffer.WriteString(", \"Record\":")
// Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")
fmt.Printf("- queryAllCars:\n%s\n", buffer.String())
return shim.Success(buffer.Bytes())
}
这个方法的作用就是将CAR0
~CAR999
索引范围内的结果,构造成一个数组,并返回。
APIstub.GetStateByRange
方法是 fabric
的shim
包中带有的方法,该方法通过遍历索引来获取对应的值。
想要了解具体的文档内容可以查看: https://godoc.org/github.com/hyperledger/fabric/core/chaincode/shim#ChaincodeStub.GetStateByRange(需要翻墙)
或者直接查看:
https://github.com/hyperledger/fabric/blob/release-1.2/core/chaincode/shim/interfaces.go
不知道你注意到没有,你执行query.js
的时候,确实返回了数据。可是这个数据是在什么时候写入的?
不知道你还记不记得,在startFabric.sh
脚本中有这样一段代码
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/Admin@org1.example.com/msp" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{"function":"initLedger","Args":[""]}'
这段代码使用 invoke
指令,调用了chaincode
的initLedger
方法,参数为空。我们再来看看chaincode
的initLedger
方法都做了些什么?
func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
cars := []Car{
Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
}
i := 0
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
}
return shim.Success(nil)
}
定义了一个切片,使用for循环语句来遍历切片,并调用APIstub.PutState
方法来将数据保存,参数分别为key
以及其对应的value
。
key的构造方式为CAR
+ i ,由于 i 是从 0 开始的,所以 key 的范围是 CAR0~ CAR9 。
总结:
query 的执行流程,应用通过 sdk 调用指定的chaincode, chaincode 对业务进行处理之后,并返回应用所需要的内容。
query 的执行过程是不会经过 orderer 的。只是在其所指定的peer节点上查询即可。而 invoke 方法则不同,需要经过 orderer 节点。
作者:沙漠中的猴
链接:https://www.jianshu.com/p/b0c11a76ff67
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。