Hyperledger Fabric 1.2系列fabcar: 4. query的执行过程

从上一篇内容我们可以看到,执行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 的位置

  1. 查看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 来查看代码吧?代码少还好说,代码多了的话。还要装一些乱七八糟的工具,想想都累。

  1. 查看启动的网络
    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/[email protected]/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实现了InitInvoke这两个方法。那么就实现了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方法是 fabricshim包中带有的方法,该方法通过遍历索引来获取对应的值。
想要了解具体的文档内容可以查看: 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/[email protected]/msp" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{"function":"initLedger","Args":[""]}'

这段代码使用 invoke指令,调用了chaincodeinitLedger方法,参数为空。我们再来看看chaincodeinitLedger方法都做了些什么?

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
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

你可能感兴趣的:(区块链技术)